From 82e3d61d120047bfdb714429a5d9beab2b0fc98f Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Wed, 19 Jul 2017 17:34:04 -0700 Subject: [PATCH 01/53] initial ae setup --- test/functional/setup/certificate.ps1 | 37 +++++++++++++ test/functional/setup/cleanup_dbs.py | 6 ++ test/functional/setup/setup_dbs.py | 30 ++++++++++ test/functional/sqlsrv/AEData.inc | 55 +++++++++++++++++++ test/functional/sqlsrv/encrypttable.ps1 | 43 +++++++++++++++ .../functional/sqlsrv/sqlsrv_fetch_ae_int.php | 38 +++++++++++++ 6 files changed, 209 insertions(+) create mode 100644 test/functional/setup/certificate.ps1 create mode 100644 test/functional/sqlsrv/AEData.inc create mode 100644 test/functional/sqlsrv/encrypttable.ps1 create mode 100644 test/functional/sqlsrv/sqlsrv_fetch_ae_int.php diff --git a/test/functional/setup/certificate.ps1 b/test/functional/setup/certificate.ps1 new file mode 100644 index 00000000..279984fa --- /dev/null +++ b/test/functional/setup/certificate.ps1 @@ -0,0 +1,37 @@ +Param( + [Parameter(Mandatory=$True,Position=1)] + [string]$serverName, + [Parameter(Mandatory=$True,Position=2)] + [string]$databaseName, + [Parameter(Mandatory=$True,Position=3)] + [string]$userName, + [Parameter(Mandatory=$True,Position=4)] + [string]$password) + +# Create a column master key in Windows Certificate Store. +$cert1 = New-SelfSignedCertificate -Subject "PHPAlwaysEncryptedCert" -CertStoreLocation Cert:CurrentUser\My -KeyExportPolicy Exportable -Type DocumentEncryptionCert -KeyUsage DataEncipherment -KeySpec KeyExchange + +# Import the SqlServer module. +Import-Module "SqlServer" + +#For SQL Server Authentication +Add-Type -AssemblyName "Microsoft.SqlServer.Smo" +$MySQL = new-object('Microsoft.SqlServer.Management.Smo.Server') $serverName +$MySQL.ConnectionContext.LoginSecure = $false +$MySQL.ConnectionContext.set_Login($userName) +$MySQL.ConnectionContext.set_Password($password) +$database = $MySQL.Databases[$databaseName] + +# Create a SqlColumnMasterKeySettings object for your column master key. +$cmkSettings = New-SqlCertificateStoreColumnMasterKeySettings -CertificateStoreLocation "CurrentUser" -Thumbprint $cert1.Thumbprint + +# Create column master key metadata in the database. +$cmkName = "CMK2" +New-SqlColumnMasterKey -Name $cmkName -InputObject $database -ColumnMasterKeySettings $cmkSettings + +# Generate a column encryption key, encrypt it with the column master key and create column encryption key metadata in the database. +$cekName = "CEK2" +New-SqlColumnEncryptionKey -Name $cekName -InputObject $database -ColumnMasterKey $cmkName + +# Disconnect +$MySQL.ConnectionContext.Disconnect() \ No newline at end of file diff --git a/test/functional/setup/cleanup_dbs.py b/test/functional/setup/cleanup_dbs.py index 36eac6ac..86406303 100644 --- a/test/functional/setup/cleanup_dbs.py +++ b/test/functional/setup/cleanup_dbs.py @@ -27,3 +27,9 @@ if __name__ == '__main__': conn_options = ' -S ' + server + ' -U ' + uid + ' -P ' + pwd + ' ' executeSQLscript( os.path.join( os.path.dirname(os.path.realpath(__file__)), 'drop_db.sql'), conn_options, args.DBNAME) + + # if Windows, remove self signed certificate using ps command + if platform.system() == 'Windows': + remove_cert_ps = "Get-ChildItem Cert:CurrentUser\My | Where-Object { $_.Subject -match 'PHPAlwaysEncryptedCert' } | Remove-Item" + inst_command = 'powershell -executionPolicy Unrestricted -command ' + remove_cert_ps + executeCommmand(inst_command) \ No newline at end of file diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index fb779d43..64f768b6 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -45,6 +45,32 @@ def executeBulkCopy(conn_options, dbname, tblname, datafile): inst_command = redirect_string.format(dbname, tblname, datafile) + conn_options executeCommmand(inst_command) +def getmsodbcsql_version( server, uid, pwd ): + command = "php -r \"echo sqlsrv_client_info( sqlsrv_connect( '{0}', array( 'UID'=>'{1}', 'PWD'=>'{2}')))['DriverVer'];\"" + p = subprocess.Popen( command.format( server, uid, pwd ), stdout=subprocess.PIPE, shell = True ) + out, err = p.communicate() + return out.decode('ascii') + +def getserver_version( server, uid, pwd ): + command = "php -r \"echo sqlsrv_server_info( sqlsrv_connect( '{0}', array( 'UID'=>'{1}', 'PWD'=>'{2}')))['SQLServerVersion'];\"" + p = subprocess.Popen( command.format( server, uid, pwd ), stdout=subprocess.PIPE, shell = True ) + out, err = p.communicate() + return out.decode('ascii') + +def is_ae_qualified( server, uid, pwd ): + msodbcsql_ver = getmsodbcsql_version( server, uid, pwd ); + server_ver = getserver_version( server, uid, pwd ); + msodbcsql_maj = msodbcsql_ver.split()[1] + msodbcsql_min = msodbcsql_ver.split()[2] + if msodbcsql_maj < 13 or ( msodbcsql_maj == 13 and msodbcsql_min == 0 ) or server_ver.split()[1] < 13: + return false + return true; + +def setupAETestDatabase( server, dbname, uid, pwd): + if platform.system() == 'Windows': + inst_command = 'powershell -executionPolicy Unrestricted certificate.ps1 ' + server + ' ' + dbname + ' ' + uid + ' ' + pwd + executeCommmand(inst_command) + if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-dbname', '--DBNAME', required=True) @@ -75,5 +101,9 @@ if __name__ == '__main__': setupTestDatabase(conn_options, args.DBNAME, args.AZURE) # populate these tables populateTables(conn_options, args.DBNAME) + + if is_ae_qualified( server, uid, pwd ): + setupAE(server, args.DBNAME, uid, pwd) + os.chdir(current_working_dir) diff --git a/test/functional/sqlsrv/AEData.inc b/test/functional/sqlsrv/AEData.inc new file mode 100644 index 00000000..63abd82b --- /dev/null +++ b/test/functional/sqlsrv/AEData.inc @@ -0,0 +1,55 @@ + \ No newline at end of file diff --git a/test/functional/sqlsrv/encrypttable.ps1 b/test/functional/sqlsrv/encrypttable.ps1 new file mode 100644 index 00000000..12370e6e --- /dev/null +++ b/test/functional/sqlsrv/encrypttable.ps1 @@ -0,0 +1,43 @@ +Param( + [Parameter(Mandatory=$True,Position=1)] + [string]$serverName, + [Parameter(Mandatory=$True,Position=2)] + [string]$databaseName, + [Parameter(Mandatory=$True,Position=3)] + [string]$userName, + [Parameter(Mandatory=$True,Position=4)] + [string]$password, + [Parameter(Mandatory=$True,Position=5)] + [string]$tableName, + [Parameter(Mandatory=$True,Position=6)] + [string]$columnNames) + +# Import the SqlServer module. +Import-Module "SqlServer" + +#For SQL Server Authentication +Add-Type -AssemblyName "Microsoft.SqlServer.Smo" +$MySQL = new-object('Microsoft.SqlServer.Management.Smo.Server') $serverName +$MySQL.ConnectionContext.LoginSecure = $false +$MySQL.ConnectionContext.set_Login($userName) +$MySQL.ConnectionContext.set_Password($password) +$database = $MySQL.Databases[$databaseName] + +#split the column names into an array +$column_arr = $columnNames.Split(",") + +# Encrypt the selected columns (or re-encrypt, if they are already encrypted using keys/encrypt types, different than the specified keys/types. +$ces = @() +foreach($col_name in $column_arr){ + $col_full_name = "$tableName.$col_name" + if($col_name -like '*det*'){ + $ces += New-SqlColumnEncryptionSettings -ColumnName $col_full_name -EncryptionType "Deterministic" -EncryptionKey "CEK1" + } + elseif($col_name -like '*rand*'){ + $ces += New-SqlColumnEncryptionSettings -ColumnName $col_full_name -EncryptionType "Randomized" -EncryptionKey "CEK1" + } +} +Set-SqlColumnEncryption -InputObject $database -ColumnEncryptionSettings $ces + +# Disconnect +$MySQL.ConnectionContext.Disconnect() \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_fetch_ae_int.php b/test/functional/sqlsrv/sqlsrv_fetch_ae_int.php new file mode 100644 index 00000000..ad1bf0d8 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_fetch_ae_int.php @@ -0,0 +1,38 @@ +"Enabled")); + + // create table + $tbname = GetTempTableName("", false); + $dataTypes = array("bigint", "int", "smallint"); + $encTypes = array("norm", "det", "rand"); + $dataTypes_str = ""; + $col_names = array(); + foreach ($dataType in $dataTypes){ + foreach ($encType in $encTypes) { + $col_name = $encType + $dataType; + $dataTypes_str = $dataTypes_str + "[" + $col_name + "] " + $dataTypes + ", "; + array_push($col_names, $col_name); + } + } + $dataTypes_str = rtrim($dataTypes_str, ", "); + CreateTableEx( $conn, $tbname, $dataTypes_str); + + // populate table + $data_arr = array_merge( array_slice($bigint_params, 0, 3), array_slice($int_params, 0, 3), array_slice($smallint_params, 0, 3) ); + $data_str = implode(", ", $data_arr); + sqlsrv_query( $conn, "INSERT INTO $tbname VALUES ( $data_str )"); + + // encrypt columns + $col_name_str = implode($col_names); + $runCMD = "powershell -executionPolicy Unrestricted encrypttable.ps1 " . $server . " " . $database . " " . $userName . " " . $userPassword . " " . $tbname . " " . $col_name_str; + shell_exec($runCMD); + + DropTable($conn, $tbname); + sqlsrv_close($conn); +} +?> \ No newline at end of file From 6b4be0c13e002de75e491187dffcd6a4834c9b22 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Thu, 20 Jul 2017 17:43:08 -0700 Subject: [PATCH 02/53] more database setup --- test/functional/setup/certificate.ps1 | 4 +- test/functional/setup/setup_dbs.py | 8 +- test/functional/sqlsrv/AEData.inc | 33 +++++++++ test/functional/sqlsrv/encrypttable.ps1 | 2 + .../functional/sqlsrv/sqlsrv_fetch_ae_int.php | 38 ---------- .../sqlsrv/sqlsrv_fetch_ae_int.phpt | 73 +++++++++++++++++++ 6 files changed, 115 insertions(+), 43 deletions(-) delete mode 100644 test/functional/sqlsrv/sqlsrv_fetch_ae_int.php create mode 100644 test/functional/sqlsrv/sqlsrv_fetch_ae_int.phpt diff --git a/test/functional/setup/certificate.ps1 b/test/functional/setup/certificate.ps1 index 279984fa..11e38817 100644 --- a/test/functional/setup/certificate.ps1 +++ b/test/functional/setup/certificate.ps1 @@ -26,11 +26,11 @@ $database = $MySQL.Databases[$databaseName] $cmkSettings = New-SqlCertificateStoreColumnMasterKeySettings -CertificateStoreLocation "CurrentUser" -Thumbprint $cert1.Thumbprint # Create column master key metadata in the database. -$cmkName = "CMK2" +$cmkName = "CMK1" New-SqlColumnMasterKey -Name $cmkName -InputObject $database -ColumnMasterKeySettings $cmkSettings # Generate a column encryption key, encrypt it with the column master key and create column encryption key metadata in the database. -$cekName = "CEK2" +$cekName = "CEK1" New-SqlColumnEncryptionKey -Name $cekName -InputObject $database -ColumnMasterKey $cmkName # Disconnect diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index 64f768b6..12532ea4 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -66,9 +66,11 @@ def is_ae_qualified( server, uid, pwd ): return false return true; -def setupAETestDatabase( server, dbname, uid, pwd): +def setupAE( server, dbname, uid, pwd): if platform.system() == 'Windows': - inst_command = 'powershell -executionPolicy Unrestricted certificate.ps1 ' + server + ' ' + dbname + ' ' + uid + ' ' + pwd + dir_name = os.path.realpath(__file__) + cert_name = os.path.join(dir_name, "certificate.ps1") + inst_command = 'powershell -executionPolicy Unrestricted -file ' + cert_name + ' ' + server + ' ' + dbname + ' ' + uid + ' ' + pwd executeCommmand(inst_command) if __name__ == '__main__': @@ -101,7 +103,7 @@ if __name__ == '__main__': setupTestDatabase(conn_options, args.DBNAME, args.AZURE) # populate these tables populateTables(conn_options, args.DBNAME) - + # setup AE (certificate, column master key and column encryption key) if is_ae_qualified( server, uid, pwd ): setupAE(server, args.DBNAME, uid, pwd) diff --git a/test/functional/sqlsrv/AEData.inc b/test/functional/sqlsrv/AEData.inc index 63abd82b..40d1096f 100644 --- a/test/functional/sqlsrv/AEData.inc +++ b/test/functional/sqlsrv/AEData.inc @@ -52,4 +52,37 @@ $varbinarymax_params = array('max indicates that the maximum storage size is 2^3 'This can create an implicit limit to the number of non-null varchar(max) or nvarchar(max) columns that can be created in a table.', 'No special error is provided when the table is created (beyond the usual warning that the maximum row size exceeds the allowed maximum of 8060 bytes) or at the time of data insertion.', 'This large row size can cause errors (such as error 512) during some normal operations, such as a clustered index key update, or sorts of the full column set, which users cannot anticipate until performing an operation.'); +// this function creates a table that contain columns of $dataTypes and all encryption types +// for example, if $dataTyptes = array("bigint", "int"), then the table created has 6 columns: +// normbigint, detbigint, randbigint, normint, detint, randint +// column names with prefix norm means it'll not be encrypted +// column names with prefix det mean it'll be encrypted with deterministic encryption +// column names with prefix rand mean it'll be encrypted with randomized encryption +// return the column names in order in the table +function CreateAETable($conn, $tableName, $dataTypes) { + include 'MsCommon.inc'; + $encTypes = array("norm", "det", "rand"); + $dataTypes_str = ""; + $col_names = array(); + foreach ($dataTypes as $dataType){ + foreach ($encTypes as $encType) { + $col_name = $encType . $dataType; + $dataTypes_str = $dataTypes_str . "[" . $col_name . "] " . $dataType . ", "; + array_push($col_names, $col_name); + } + } + $dataTypes_str = rtrim($dataTypes_str, ", "); + CreateTableEx( $conn, $tbname, $dataTypes_str); + return $col_names; +} + +function EncryptColumns($col_names){ + include 'MsCommon.inc'; + $dir_name = realpath(dirname(__FILE__)); + $enc_name = $dir_name . DIRECTORY_SEPARATOR . "encrypttable.ps1"; + $col_name_str = implode(",", $col_names); + $runCMD = "powershell -executionPolicy Unrestricted -file " . $enc_name . " " . $server . " " . $database . " " . $userName . " " . $userPassword . " " . $tbname . " " . $col_name_str; + $retval = shell_exec($runCMD); +} + ?> \ No newline at end of file diff --git a/test/functional/sqlsrv/encrypttable.ps1 b/test/functional/sqlsrv/encrypttable.ps1 index 12370e6e..0e14ce8e 100644 --- a/test/functional/sqlsrv/encrypttable.ps1 +++ b/test/functional/sqlsrv/encrypttable.ps1 @@ -15,6 +15,8 @@ Param( # Import the SqlServer module. Import-Module "SqlServer" +Write-Host $columnNames + #For SQL Server Authentication Add-Type -AssemblyName "Microsoft.SqlServer.Smo" $MySQL = new-object('Microsoft.SqlServer.Management.Smo.Server') $serverName diff --git a/test/functional/sqlsrv/sqlsrv_fetch_ae_int.php b/test/functional/sqlsrv/sqlsrv_fetch_ae_int.php deleted file mode 100644 index ad1bf0d8..00000000 --- a/test/functional/sqlsrv/sqlsrv_fetch_ae_int.php +++ /dev/null @@ -1,38 +0,0 @@ -"Enabled")); - - // create table - $tbname = GetTempTableName("", false); - $dataTypes = array("bigint", "int", "smallint"); - $encTypes = array("norm", "det", "rand"); - $dataTypes_str = ""; - $col_names = array(); - foreach ($dataType in $dataTypes){ - foreach ($encType in $encTypes) { - $col_name = $encType + $dataType; - $dataTypes_str = $dataTypes_str + "[" + $col_name + "] " + $dataTypes + ", "; - array_push($col_names, $col_name); - } - } - $dataTypes_str = rtrim($dataTypes_str, ", "); - CreateTableEx( $conn, $tbname, $dataTypes_str); - - // populate table - $data_arr = array_merge( array_slice($bigint_params, 0, 3), array_slice($int_params, 0, 3), array_slice($smallint_params, 0, 3) ); - $data_str = implode(", ", $data_arr); - sqlsrv_query( $conn, "INSERT INTO $tbname VALUES ( $data_str )"); - - // encrypt columns - $col_name_str = implode($col_names); - $runCMD = "powershell -executionPolicy Unrestricted encrypttable.ps1 " . $server . " " . $database . " " . $userName . " " . $userPassword . " " . $tbname . " " . $col_name_str; - shell_exec($runCMD); - - DropTable($conn, $tbname); - sqlsrv_close($conn); -} -?> \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_fetch_ae_int.phpt b/test/functional/sqlsrv/sqlsrv_fetch_ae_int.phpt new file mode 100644 index 00000000..77bc68cb --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_fetch_ae_int.phpt @@ -0,0 +1,73 @@ +--TEST-- +Test for fetching integer columns with column encryption +--SKIPIF-- +--FILE-- +"Enabled")); +//$conn = Connect(); + +// create table +$tbname = GetTempTableName("", false); +$dataTypes = array("bigint", "int", "smallint"); +$encTypes = array("norm", "det", "rand"); +$dataTypes_str = ""; +$col_names = array(); +foreach ($dataTypes as $dataType){ + foreach ($encTypes as $encType) { + $col_name = $encType . $dataType; + $dataTypes_str = $dataTypes_str . "[" . $col_name . "] " . $dataType . ", "; + array_push($col_names, $col_name); + } +} +$dataTypes_str = rtrim($dataTypes_str, ", "); +CreateTableEx( $conn, $tbname, $dataTypes_str); + +// populate table +$data_arr = array_merge( array_slice($bigint_params, 0, 3), array_slice($int_params, 0, 3), array_slice($smallint_params, 0, 3) ); +$data_str = implode(", ", $data_arr); +sqlsrv_query( $conn, "INSERT INTO $tbname VALUES ( $data_str )"); + +// encrypt columns +$dir_name = realpath(dirname(__FILE__)); +$enc_name = $dir_name . DIRECTORY_SEPARATOR . "encrypttable.ps1"; +$col_name_str = implode(",", $col_names); +$runCMD = "powershell -executionPolicy Unrestricted -file " . $enc_name . " " . $server . " " . $database . " " . $userName . " " . $userPassword . " " . $tbname . " " . $col_name_str; +$retval = shell_exec($runCMD); + +//Fetch encrypted values with ColumnEncryption Enabled +$sql = "SELECT * FROM $tbname"; +$stmt = sqlsrv_query($conn, $sql); +$decrypted_row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + +var_dump($decrypted_row); + +DropTable($conn, $tbname); +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +?> +--EXPECT-- +array(9) { + [0]=> + string(10) "2147483648" + [1]=> + string(19) "-922337203685479936" + [2]=> + string(18) "922337203685479936" + [3]=> + int(32768) + [4]=> + int(-2147483647) + [5]=> + int(2147483647) + [6]=> + int(256) + [7]=> + int(-32767) + [8]=> + int(32767) +} \ No newline at end of file From 2a0f42e02a714db846e61dd36b831f87ece7d612 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Fri, 21 Jul 2017 09:46:32 -0700 Subject: [PATCH 03/53] refactor sqlsrv_fetch_ae_int.phpt --- test/functional/sqlsrv/AEData.inc | 28 ++++++++----------- .../sqlsrv/sqlsrv_fetch_ae_int.phpt | 17 ++--------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/test/functional/sqlsrv/AEData.inc b/test/functional/sqlsrv/AEData.inc index 40d1096f..d7037854 100644 --- a/test/functional/sqlsrv/AEData.inc +++ b/test/functional/sqlsrv/AEData.inc @@ -1,5 +1,4 @@ \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_fetch_ae_int.phpt b/test/functional/sqlsrv/sqlsrv_fetch_ae_int.phpt index 77bc68cb..102fbbaf 100644 --- a/test/functional/sqlsrv/sqlsrv_fetch_ae_int.phpt +++ b/test/functional/sqlsrv/sqlsrv_fetch_ae_int.phpt @@ -13,17 +13,8 @@ $conn = Connect(array("ColumnEncryption"=>"Enabled")); // create table $tbname = GetTempTableName("", false); $dataTypes = array("bigint", "int", "smallint"); -$encTypes = array("norm", "det", "rand"); -$dataTypes_str = ""; $col_names = array(); -foreach ($dataTypes as $dataType){ - foreach ($encTypes as $encType) { - $col_name = $encType . $dataType; - $dataTypes_str = $dataTypes_str . "[" . $col_name . "] " . $dataType . ", "; - array_push($col_names, $col_name); - } -} -$dataTypes_str = rtrim($dataTypes_str, ", "); +$dataTypes_str = get_dataTypes_str($dataTypes, $col_names); CreateTableEx( $conn, $tbname, $dataTypes_str); // populate table @@ -32,11 +23,7 @@ $data_str = implode(", ", $data_arr); sqlsrv_query( $conn, "INSERT INTO $tbname VALUES ( $data_str )"); // encrypt columns -$dir_name = realpath(dirname(__FILE__)); -$enc_name = $dir_name . DIRECTORY_SEPARATOR . "encrypttable.ps1"; -$col_name_str = implode(",", $col_names); -$runCMD = "powershell -executionPolicy Unrestricted -file " . $enc_name . " " . $server . " " . $database . " " . $userName . " " . $userPassword . " " . $tbname . " " . $col_name_str; -$retval = shell_exec($runCMD); +EncryptColumns($server, $database, $userName, $userPassword, $tbname, $col_names); //Fetch encrypted values with ColumnEncryption Enabled $sql = "SELECT * FROM $tbname"; From 62572f630b5a283d4133c6367868ee063f719d73 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 24 Jul 2017 13:33:52 -0700 Subject: [PATCH 04/53] import certificate and generate CMK and CEK using tsql, no long depends on SSMS --- test/functional/setup/PHPcert.pfx | Bin 0 -> 2574 bytes test/functional/setup/ae_keys.sql | 38 ++++++++ test/functional/setup/certificate.ps1 | 37 -------- test/functional/setup/setup_dbs.py | 9 +- test/functional/sqlsrv/AEData.inc | 84 ------------------ .../sqlsrv/sqlsrv_fetch_ae_int.phpt | 60 ------------- .../functional/sqlsrv/test_ae_keys_setup.phpt | 32 +++++++ 7 files changed, 76 insertions(+), 184 deletions(-) create mode 100644 test/functional/setup/PHPcert.pfx create mode 100644 test/functional/setup/ae_keys.sql delete mode 100644 test/functional/setup/certificate.ps1 delete mode 100644 test/functional/sqlsrv/AEData.inc delete mode 100644 test/functional/sqlsrv/sqlsrv_fetch_ae_int.phpt create mode 100644 test/functional/sqlsrv/test_ae_keys_setup.phpt diff --git a/test/functional/setup/PHPcert.pfx b/test/functional/setup/PHPcert.pfx new file mode 100644 index 0000000000000000000000000000000000000000..742a217c88350cbd0e8eab87409de9c9235b0dbf GIT binary patch literal 2574 zcmZXUcU05a632fDAxQ7iq%BAl4Iw}zp?9UXU8Jgns$zly(g{TcL4goZPv z3rNcvq)M*>p|eysdfwYT@4R!*+?n~z_s+fl+=1a47{DMJ7@qzklwKym z)7L@p^i@<`0mDOq|0&UKLGZNeFg)!F)fS+P|2f6P00L*?A!jf=%M1|i<#=VtD%(L;ZKowY)!sTM4=NG z=G|)2*5{D1I|(5b1Dq7xkqElrfFzjGiFK~;azfn!=~ZwiSrO8VWxSHb7ffG%94vYx zSt@#$wK!iO%5$r^K4q}bnAVERj4ezL@}x5;{~Pf>i`d}HCm194BLmw>k=P{hK3^XH zo-nc^H?49n1*v+6pGdej9?b8!gCMyVYPRzNM(Ef$wW^Do=1n5Ce(1F12c`6jAA#+{v3yIMT??9FUd!yN8BtPk7GDs@R5rVSnP?h_G=X{wlVja0^37DK3A)U z;pRf!(dLT3(d+t-rW9$ToKOW4H1N5m7^{tt%HH>cC`td5Ka1rdifhVBIf@471NHf{ z1&NSy-1uoU0gxE2?7zYYCM9&gvqre^sZvD=cfN6V>GYvNh36qbu- zLn+yj=$2lUP*^R2BHJj2b*Y@>Fw^456m6fh&(iWei?(DHRb6dk_wudi=&8KB0(ABk zN;7b=St~pLAeogByJON_eS-TZzn3{YSC=0RYd{k2)nxeEmyhNcB>hFc_1sFlYSd}M zjwv#JB^l;(ilNUnDsBu-3nn9ly^RYxlDd*hd_Kx5vdqYeFgFq2ttDTmR_j{|Z}_~a z4kmsZk5vB{+68*}UYjy{Mq}!rj8Wq#A4ibijfukH1$n>gVKr0TA|^Xm%|4wd7X(s@ z6;&vdRy|TY%99}|Dc@+t^LhcfZ)Vy>&iB|)_82~9!mAw^p;iwcs@As(q~mi@*wmB_ zsPm$CW^$AT^rg!{?LE`z^3|C;Un@?lKV)?nupwtaawb88-)E&W&UA5CMbGD`Sf$9` z4m-rFsfKc7Yiz`{81^OT78}6DD~xXhb?ADk48+6(!BnE|AX71z3LAHavk}|Ug z!Ak~il8Du|$%wjjum=dWj9<+B$o4AZ^)1fOH;e&mHzealm_%w@AWN0$zQYlVf&Fd4 z?S+_x3GEh^ds`XZTn*~EjS0TJ5iF{3VyVQzVOcZP_5q$e9@HH_AtL(362xr3|H<4hh`mT!}U_2N?fmCEbR)wLnqp7*DyF$d(( z7s@YnkhGki!G>04kRDGNp{c993d8U^fmwvbOx5DoSr47n>gZz)!^Hr*Vyz*5eFA+y z+muSH=qG7@@D<1X4<%0*MVVQrDFWSk0&U&9nsT}h7P79{?9Xkcbq_3-{Yv?d>~q*V zCdize0_isL;eWz7{~4F`-11Ne1Oxy8%;8_iC2uBV0z3dPfE=I%Ac5O}BGrC)c`AWZ z`EBZw0}ww6k{aEl>fnIuPyH>b8cwaaPrMpA2tAMItKAP5Mr%nth5-Wc$pig188 z;6pW6>UAQ3C3XKn?g4JpOq&`%0`60pH#NsmFS!r6|2!oEnEllGQZpCob8s*`O~F6! zr-NqVX;P_(00se67x!-k^#6l@ei>bhjByojJ8iENbd;WY{Ybe9>;)cQ@wuE~ z;z#lv&yc#d8EvCrbC5+;PGhUl?|NHAgAOoJNPK%4Z})0HBWZc8yrg*S=J0#UxggB+ z{7_(IN>ERdlwt0!Kg#Lifvw-Rl#(V1+x|RU@Wt|-HK-leS_O~kk|Xhr?sHo@4J}Kd z!;`SDeu?q0l8T&PzYerj|B}~_UE0J(>TN+YV2$LEXp33Gnh|EkiDWYLwq!rb!>J2( zJ)sE8)G`4HWJ$8r=r-0_I9?pSyo-YvZiIq95A2z1MFbkb#&4wmK844J8-#>}Mxl2D z3+*O{zeq9~271WhzTm5;R4SOc-`G6oBevwa^p!Oq8DCE}dX*ulKMZTFi7i{Wav&HK z~j{8C{xt*IK3AJugQn<#YcZP`(* zVM?F%0A|B4sDYB;z)ZJ`#xajMi?x-9k|)Qp!e_UQufejV2({*3h6f0~q+eNJ@~uy^ zD@_koGSk0>2gL1>!FA_PFITRl3B%?W1{lSN5NT8n_u)uCLBepWjVFdZC8YTFK+_qC z#46wMcxS19GYO{8tR;@>Zx|aLZ+$EK;3ZDoitJwT8f|coJv){!}J%8y& za_sTiNbsJ~5Yy;n|bgjs3VWnI?kn9v6%u~JIMBjKN%J=Z*wtJF{iliMas zmhbB^WAV!f-knTKj1V?~tnb!)e*dxBPVY-UKGs^v-+qznwFI#0P}Ep^_-Q=Jd@Hmy zYG;*EJY*TfF{;qWKccmyWb}QeAxm(J+s*+mT8%Nu+Z@0(s;&kchYAtG_NG|gU$N#~ z`i3tm(n;3QYu8 a9o%gZ9{g=oYKSumZ)(j9;bQ;sLH`E%jF>I} literal 0 HcmV?d00001 diff --git a/test/functional/setup/ae_keys.sql b/test/functional/setup/ae_keys.sql new file mode 100644 index 00000000..aa4b9d78 --- /dev/null +++ b/test/functional/setup/ae_keys.sql @@ -0,0 +1,38 @@ +USE $(dbname) +GO + +/* DROP Column Encryption Key first, Column Master Key cannot be dropped until no encryption depends on it */ +IF EXISTS (SELECT * FROM sys.column_encryption_keys WHERE [name] LIKE '%AEColumnKey%') + +BEGIN +DROP COLUMN ENCRYPTION KEY [AEColumnKey] +END +GO + +/* Can finally drop Column Master Key after the Encryption Key is dropped */ +IF EXISTS (SELECT * FROM sys.column_master_keys WHERE [name] LIKE '%AEMasterKey%') + +BEGIN +DROP COLUMN MASTER KEY [AEMasterKey] +END +GO + +/* Recreate the Column Master Key */ +CREATE COLUMN MASTER KEY [AEMasterKey] +WITH +( + KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE', + KEY_PATH = N'CurrentUser/my/237F94738E7F5214D8588006C2269DBC6B370816' +) +GO + +/* Create Column Encryption Key using the Column Master Key */ +/* ENCRYPTED_VALUE is generated by SSMS and it is always the same if the same Certificate is imported */ +CREATE COLUMN ENCRYPTION KEY [AEColumnKey] +WITH VALUES +( + COLUMN_MASTER_KEY = [AEMasterKey], + ALGORITHM = 'RSA_OAEP', + ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F00320033003700660039003400370033003800650037006600350032003100340064003800350038003800300030003600630032003200360039006400620063003600620033003700300038003100360039DE2397A08F6313E7820D75382D8469BE1C8F3CD47E3240A5A6D6F82D322F6EB1B103C9C47999A69FFB164D37E7891F60FFDB04ADEADEB990BE88AE488CAFB8774442DF909D2EF8BB5961A5C11B85BA7903E0E453B27B49CE0A30D14FF4F412B5737850A4C564B44C744E690E78FAECF007F9005E3E0FB4F8D6C13B016A6393B84BB3F83FEED397C4E003FF8C5BBDDC1F6156349A8B40EDC26398C9A03920DD81B9197BC83A7378F79ECB430A04B4CFDF3878B0219BB629F5B5BF3C2359A7498AD9A6F5D63EF15E060CDB10A65E6BF059C7A32237F0D9E00C8AC632CCDD68230774477D4F2E411A0E4D9B351E8BAA87793E64456370D91D4420B5FD9A252F6D9178AE3DD02E1ED57B7F7008114272419F505CBCEB109715A6C4331DEEB73653990A7140D7F83089B445C59E4858809D139658DC8B2781CB27A749F1CE349DC43238E1FBEAE0155BF2DBFEF6AFD9FD2BD1D14CEF9AC125523FD1120488F24416679A6041184A2719B0FC32B6C393FF64D353A3FA9BC4FA23DFDD999B0771A547B561D72B92A0B2BB8B266BC25191F2A0E2F8D93648F8750308DCD79BE55A2F8D5FBE9285265BEA66173CD5F5F21C22CC933AE2147F46D22BFF329F6A712B3D19A6488DDEB6FDAA5B136B29ADB0BA6B6D1FD6FBA5D6A14F76491CB000FEE4769D5B268A3BF50EA3FBA713040944558EDE99D38A5828E07B05236A4475DA27915E +) +GO \ No newline at end of file diff --git a/test/functional/setup/certificate.ps1 b/test/functional/setup/certificate.ps1 deleted file mode 100644 index 11e38817..00000000 --- a/test/functional/setup/certificate.ps1 +++ /dev/null @@ -1,37 +0,0 @@ -Param( - [Parameter(Mandatory=$True,Position=1)] - [string]$serverName, - [Parameter(Mandatory=$True,Position=2)] - [string]$databaseName, - [Parameter(Mandatory=$True,Position=3)] - [string]$userName, - [Parameter(Mandatory=$True,Position=4)] - [string]$password) - -# Create a column master key in Windows Certificate Store. -$cert1 = New-SelfSignedCertificate -Subject "PHPAlwaysEncryptedCert" -CertStoreLocation Cert:CurrentUser\My -KeyExportPolicy Exportable -Type DocumentEncryptionCert -KeyUsage DataEncipherment -KeySpec KeyExchange - -# Import the SqlServer module. -Import-Module "SqlServer" - -#For SQL Server Authentication -Add-Type -AssemblyName "Microsoft.SqlServer.Smo" -$MySQL = new-object('Microsoft.SqlServer.Management.Smo.Server') $serverName -$MySQL.ConnectionContext.LoginSecure = $false -$MySQL.ConnectionContext.set_Login($userName) -$MySQL.ConnectionContext.set_Password($password) -$database = $MySQL.Databases[$databaseName] - -# Create a SqlColumnMasterKeySettings object for your column master key. -$cmkSettings = New-SqlCertificateStoreColumnMasterKeySettings -CertificateStoreLocation "CurrentUser" -Thumbprint $cert1.Thumbprint - -# Create column master key metadata in the database. -$cmkName = "CMK1" -New-SqlColumnMasterKey -Name $cmkName -InputObject $database -ColumnMasterKeySettings $cmkSettings - -# Generate a column encryption key, encrypt it with the column master key and create column encryption key metadata in the database. -$cekName = "CEK1" -New-SqlColumnEncryptionKey -Name $cekName -InputObject $database -ColumnMasterKey $cmkName - -# Disconnect -$MySQL.ConnectionContext.Disconnect() \ No newline at end of file diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index 12532ea4..49cb86a5 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -68,10 +68,13 @@ def is_ae_qualified( server, uid, pwd ): def setupAE( server, dbname, uid, pwd): if platform.system() == 'Windows': + # import self signed certificate dir_name = os.path.realpath(__file__) - cert_name = os.path.join(dir_name, "certificate.ps1") - inst_command = 'powershell -executionPolicy Unrestricted -file ' + cert_name + ' ' + server + ' ' + dbname + ' ' + uid + ' ' + pwd - executeCommmand(inst_command) + cert_name = os.path.join(dir_name, "PHPcert.ps1") + inst_command = "certutil -user -p '' -importPFX My " + cert_name + " NoRoot" + executeCommand(inst_command) + # create Column Master Key and Column Encryption Key + executeSQLscript('ae_keys.sql', conn_options, dbname) if __name__ == '__main__': parser = argparse.ArgumentParser() diff --git a/test/functional/sqlsrv/AEData.inc b/test/functional/sqlsrv/AEData.inc deleted file mode 100644 index d7037854..00000000 --- a/test/functional/sqlsrv/AEData.inc +++ /dev/null @@ -1,84 +0,0 @@ - \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_fetch_ae_int.phpt b/test/functional/sqlsrv/sqlsrv_fetch_ae_int.phpt deleted file mode 100644 index 102fbbaf..00000000 --- a/test/functional/sqlsrv/sqlsrv_fetch_ae_int.phpt +++ /dev/null @@ -1,60 +0,0 @@ ---TEST-- -Test for fetching integer columns with column encryption ---SKIPIF-- ---FILE-- -"Enabled")); -//$conn = Connect(); - -// create table -$tbname = GetTempTableName("", false); -$dataTypes = array("bigint", "int", "smallint"); -$col_names = array(); -$dataTypes_str = get_dataTypes_str($dataTypes, $col_names); -CreateTableEx( $conn, $tbname, $dataTypes_str); - -// populate table -$data_arr = array_merge( array_slice($bigint_params, 0, 3), array_slice($int_params, 0, 3), array_slice($smallint_params, 0, 3) ); -$data_str = implode(", ", $data_arr); -sqlsrv_query( $conn, "INSERT INTO $tbname VALUES ( $data_str )"); - -// encrypt columns -EncryptColumns($server, $database, $userName, $userPassword, $tbname, $col_names); - -//Fetch encrypted values with ColumnEncryption Enabled -$sql = "SELECT * FROM $tbname"; -$stmt = sqlsrv_query($conn, $sql); -$decrypted_row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); - -var_dump($decrypted_row); - -DropTable($conn, $tbname); -sqlsrv_free_stmt($stmt); -sqlsrv_close($conn); - -?> ---EXPECT-- -array(9) { - [0]=> - string(10) "2147483648" - [1]=> - string(19) "-922337203685479936" - [2]=> - string(18) "922337203685479936" - [3]=> - int(32768) - [4]=> - int(-2147483647) - [5]=> - int(2147483647) - [6]=> - int(256) - [7]=> - int(-32767) - [8]=> - int(32767) -} \ No newline at end of file diff --git a/test/functional/sqlsrv/test_ae_keys_setup.phpt b/test/functional/sqlsrv/test_ae_keys_setup.phpt new file mode 100644 index 00000000..67be3064 --- /dev/null +++ b/test/functional/sqlsrv/test_ae_keys_setup.phpt @@ -0,0 +1,32 @@ +--TEST-- +retrieval of names of column master key and column encryption key generated in the database setup +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Column Master Key generated: AEMasterKey +Column Encryption Key generated: AEColumnKey \ No newline at end of file From 60514496575a8b6c925881d0fe0247ac7f71847b Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 24 Jul 2017 14:34:05 -0700 Subject: [PATCH 05/53] added IsAEQualified --- test/functional/setup/setup_dbs.py | 6 ++-- test/functional/sqlsrv/MsCommon.inc | 19 ++++++++++--- .../functional/sqlsrv/test_ae_keys_setup.phpt | 28 +++++++++++-------- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index 49cb86a5..c44a644b 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -60,9 +60,9 @@ def getserver_version( server, uid, pwd ): def is_ae_qualified( server, uid, pwd ): msodbcsql_ver = getmsodbcsql_version( server, uid, pwd ); server_ver = getserver_version( server, uid, pwd ); - msodbcsql_maj = msodbcsql_ver.split()[1] - msodbcsql_min = msodbcsql_ver.split()[2] - if msodbcsql_maj < 13 or ( msodbcsql_maj == 13 and msodbcsql_min == 0 ) or server_ver.split()[1] < 13: + msodbcsql_maj = msodbcsql_ver.split('.')[1] + msodbcsql_min = msodbcsql_ver.split('.')[2] + if msodbcsql_maj < 13 or ( msodbcsql_maj == 13 and msodbcsql_min == 0 ) or server_ver.split('.')[1] < 13: return false return true; diff --git a/test/functional/sqlsrv/MsCommon.inc b/test/functional/sqlsrv/MsCommon.inc index ae32bee6..7782fe32 100644 --- a/test/functional/sqlsrv/MsCommon.inc +++ b/test/functional/sqlsrv/MsCommon.inc @@ -80,6 +80,17 @@ function IsDaasMode() return ($daasMode ? true : false); } +function IsAEQualified($conn) +{ + $msodbcsql_ver = sqlsrv_client_info($conn)['DriverVer']; + $server_ver = sqlsrv_server_info($conn)['SQLServerVersion']; + $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; + $msodbcsql_min = explode(".", $msodbcsql_ver)[1]; + if ($msodbcsql_maj < 13 || ( $msodbcsql_maj == 13 && $msodbcsql_min == 0 ) || explode('.')[0] < 13) + return false; + return true; +} + function StartTest($testName) { include 'MsSetup.inc'; @@ -165,7 +176,7 @@ function GetTempTableName($table = '', $temporary = true) // dropped once the connection is closed. Otherwise, the caller // should take care of dropping the temp table afterwards. - $someNumber = rand(0, 1000); + $timestamp = round(microtime(true)*1000); $prefix = ''; if ($temporary) @@ -174,7 +185,7 @@ function GetTempTableName($table = '', $temporary = true) if (strlen($table) == 0) $table = 'php_test_table'; - return $prefix . $table . '_' . $someNumber; + return $prefix . $table . '_' . $timestamp; } function GetTempProcName($proc = '', $temporary = true) @@ -183,7 +194,7 @@ function GetTempProcName($proc = '', $temporary = true) // automatically dropped once the connection is closed. Otherwise, // the caller should take care of dropping the temp procedure afterwards. - $someNumber = rand(0, 1000); + $timestamp = round(microtime(true)*1000); $prefix = ''; if ($temporary) @@ -192,7 +203,7 @@ function GetTempProcName($proc = '', $temporary = true) if (strlen($proc) == 0) $proc = 'php_test_proc'; - return $prefix . $proc . '_' . $someNumber; + return $prefix . $proc . '_' . $timestamp; } function ExecuteQuery($conn, $query) diff --git a/test/functional/sqlsrv/test_ae_keys_setup.phpt b/test/functional/sqlsrv/test_ae_keys_setup.phpt index 67be3064..d1c212c9 100644 --- a/test/functional/sqlsrv/test_ae_keys_setup.phpt +++ b/test/functional/sqlsrv/test_ae_keys_setup.phpt @@ -1,15 +1,16 @@ --TEST-- retrieval of names of column master key and column encryption key generated in the database setup --SKIPIF-- - + --FILE-- --EXPECT-- -Column Master Key generated: AEMasterKey -Column Encryption Key generated: AEColumnKey \ No newline at end of file +Test Successfully. \ No newline at end of file From 312578507f2a3f7fdc01043766b61ff9dfbbb909 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 24 Jul 2017 15:10:05 -0700 Subject: [PATCH 06/53] check if cert exist in test_ae_keys_setup.phpt --- test/functional/sqlsrv/MsCommon.inc | 3 +-- test/functional/sqlsrv/test_ae_keys_setup.phpt | 12 +++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/test/functional/sqlsrv/MsCommon.inc b/test/functional/sqlsrv/MsCommon.inc index 7782fe32..0bc0800e 100644 --- a/test/functional/sqlsrv/MsCommon.inc +++ b/test/functional/sqlsrv/MsCommon.inc @@ -85,8 +85,7 @@ function IsAEQualified($conn) $msodbcsql_ver = sqlsrv_client_info($conn)['DriverVer']; $server_ver = sqlsrv_server_info($conn)['SQLServerVersion']; $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; - $msodbcsql_min = explode(".", $msodbcsql_ver)[1]; - if ($msodbcsql_maj < 13 || ( $msodbcsql_maj == 13 && $msodbcsql_min == 0 ) || explode('.')[0] < 13) + if ($msodbcsql_maj < 13 || explode('.', $server_ver)[0] < 13) return false; return true; } diff --git a/test/functional/sqlsrv/test_ae_keys_setup.phpt b/test/functional/sqlsrv/test_ae_keys_setup.phpt index d1c212c9..0905e4b6 100644 --- a/test/functional/sqlsrv/test_ae_keys_setup.phpt +++ b/test/functional/sqlsrv/test_ae_keys_setup.phpt @@ -1,7 +1,7 @@ --TEST-- retrieval of names of column master key and column encryption key generated in the database setup --SKIPIF-- - + --FILE-- Date: Mon, 24 Jul 2017 15:27:10 -0700 Subject: [PATCH 07/53] added failing message in test_ae_keys_setup.phpt --- test/functional/sqlsrv/test_ae_keys_setup.phpt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/functional/sqlsrv/test_ae_keys_setup.phpt b/test/functional/sqlsrv/test_ae_keys_setup.phpt index 0905e4b6..d2710999 100644 --- a/test/functional/sqlsrv/test_ae_keys_setup.phpt +++ b/test/functional/sqlsrv/test_ae_keys_setup.phpt @@ -17,6 +17,9 @@ if (IsAEQualified($conn)){ { $cert_exists = true; } + else { + die("Certificate does not exist.\n"; + } $query = "SELECT name FROM sys.column_master_keys"; $stmt = sqlsrv_query($conn, $query); @@ -31,6 +34,9 @@ if (IsAEQualified($conn)){ if ($cert_exists && $master_key_name == 'AEMasterKey' && $encryption_key_name == 'AEColumnKey'){ echo "Test Successfully.\n"; } + else { + die("Column Master Key and Column Encryption Key not created.\n"); + } sqlsrv_free_stmt($stmt); } else { From 9028fd5a602138766dabf25fbc0501bc03bbdad3 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 24 Jul 2017 15:30:35 -0700 Subject: [PATCH 08/53] fix certificate path --- test/functional/setup/setup_dbs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index c44a644b..dc6ddf10 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -70,7 +70,7 @@ def setupAE( server, dbname, uid, pwd): if platform.system() == 'Windows': # import self signed certificate dir_name = os.path.realpath(__file__) - cert_name = os.path.join(dir_name, "PHPcert.ps1") + cert_name = os.path.join(dir_name, "PHPcert.pfx") inst_command = "certutil -user -p '' -importPFX My " + cert_name + " NoRoot" executeCommand(inst_command) # create Column Master Key and Column Encryption Key From ae623333c240ab28e8e64a464f5343249d9f2f4b Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 24 Jul 2017 15:38:41 -0700 Subject: [PATCH 09/53] fix test --- test/functional/sqlsrv/test_ae_keys_setup.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/sqlsrv/test_ae_keys_setup.phpt b/test/functional/sqlsrv/test_ae_keys_setup.phpt index d2710999..43f8d667 100644 --- a/test/functional/sqlsrv/test_ae_keys_setup.phpt +++ b/test/functional/sqlsrv/test_ae_keys_setup.phpt @@ -18,7 +18,7 @@ if (IsAEQualified($conn)){ $cert_exists = true; } else { - die("Certificate does not exist.\n"; + die("Certificate does not exist.\n"); } $query = "SELECT name FROM sys.column_master_keys"; From 9c0626a8782d23275a501f12b58b206ea2f13352 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 24 Jul 2017 16:03:35 -0700 Subject: [PATCH 10/53] fix setupAE params --- test/functional/setup/setup_dbs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index dc6ddf10..0c457d25 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -66,7 +66,7 @@ def is_ae_qualified( server, uid, pwd ): return false return true; -def setupAE( server, dbname, uid, pwd): +def setupAE( conn_options, dbname ): if platform.system() == 'Windows': # import self signed certificate dir_name = os.path.realpath(__file__) From e737ffe34e1437accefb5225c109bb182c34337a Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 24 Jul 2017 16:28:09 -0700 Subject: [PATCH 11/53] debug --- test/functional/setup/setup_dbs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index 0c457d25..e63675f4 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -72,6 +72,8 @@ def setupAE( conn_options, dbname ): dir_name = os.path.realpath(__file__) cert_name = os.path.join(dir_name, "PHPcert.pfx") inst_command = "certutil -user -p '' -importPFX My " + cert_name + " NoRoot" + print("********************************") + print(inst_command) executeCommand(inst_command) # create Column Master Key and Column Encryption Key executeSQLscript('ae_keys.sql', conn_options, dbname) From 9fa6d75b72fb9b8b8bc42596cfb81726e46d9665 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 24 Jul 2017 16:30:35 -0700 Subject: [PATCH 12/53] debug setup.dbs.py --- test/functional/setup/setup_dbs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index e63675f4..4b54aee6 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -67,12 +67,13 @@ def is_ae_qualified( server, uid, pwd ): return true; def setupAE( conn_options, dbname ): + print("**********In setupAE**********") if platform.system() == 'Windows': # import self signed certificate dir_name = os.path.realpath(__file__) cert_name = os.path.join(dir_name, "PHPcert.pfx") inst_command = "certutil -user -p '' -importPFX My " + cert_name + " NoRoot" - print("********************************") + print("**********In Windows**********") print(inst_command) executeCommand(inst_command) # create Column Master Key and Column Encryption Key @@ -109,7 +110,9 @@ if __name__ == '__main__': # populate these tables populateTables(conn_options, args.DBNAME) # setup AE (certificate, column master key and column encryption key) + print("**********Before is_ae_qualified**********") if is_ae_qualified( server, uid, pwd ): + print("**********In is_ae_qualified**********") setupAE(server, args.DBNAME, uid, pwd) os.chdir(current_working_dir) From 4f5207688a0c0d6d5b48337c4e589f632e25ff38 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 24 Jul 2017 17:03:23 -0700 Subject: [PATCH 13/53] debug --- test/functional/setup/setup_dbs.py | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index 4b54aee6..d67f5a75 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -45,27 +45,6 @@ def executeBulkCopy(conn_options, dbname, tblname, datafile): inst_command = redirect_string.format(dbname, tblname, datafile) + conn_options executeCommmand(inst_command) -def getmsodbcsql_version( server, uid, pwd ): - command = "php -r \"echo sqlsrv_client_info( sqlsrv_connect( '{0}', array( 'UID'=>'{1}', 'PWD'=>'{2}')))['DriverVer'];\"" - p = subprocess.Popen( command.format( server, uid, pwd ), stdout=subprocess.PIPE, shell = True ) - out, err = p.communicate() - return out.decode('ascii') - -def getserver_version( server, uid, pwd ): - command = "php -r \"echo sqlsrv_server_info( sqlsrv_connect( '{0}', array( 'UID'=>'{1}', 'PWD'=>'{2}')))['SQLServerVersion'];\"" - p = subprocess.Popen( command.format( server, uid, pwd ), stdout=subprocess.PIPE, shell = True ) - out, err = p.communicate() - return out.decode('ascii') - -def is_ae_qualified( server, uid, pwd ): - msodbcsql_ver = getmsodbcsql_version( server, uid, pwd ); - server_ver = getserver_version( server, uid, pwd ); - msodbcsql_maj = msodbcsql_ver.split('.')[1] - msodbcsql_min = msodbcsql_ver.split('.')[2] - if msodbcsql_maj < 13 or ( msodbcsql_maj == 13 and msodbcsql_min == 0 ) or server_ver.split('.')[1] < 13: - return false - return true; - def setupAE( conn_options, dbname ): print("**********In setupAE**********") if platform.system() == 'Windows': @@ -110,10 +89,7 @@ if __name__ == '__main__': # populate these tables populateTables(conn_options, args.DBNAME) # setup AE (certificate, column master key and column encryption key) - print("**********Before is_ae_qualified**********") - if is_ae_qualified( server, uid, pwd ): - print("**********In is_ae_qualified**********") - setupAE(server, args.DBNAME, uid, pwd) + setupAE(server, args.DBNAME, uid, pwd) os.chdir(current_working_dir) From 3dcb8f201df1b6b84df4cf0bc7c5eb8217d4a20a Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 24 Jul 2017 17:05:49 -0700 Subject: [PATCH 14/53] debug --- test/functional/setup/setup_dbs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index d67f5a75..46dc5921 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -89,7 +89,7 @@ if __name__ == '__main__': # populate these tables populateTables(conn_options, args.DBNAME) # setup AE (certificate, column master key and column encryption key) - setupAE(server, args.DBNAME, uid, pwd) + setupAE(conn_options args.DBNAME) os.chdir(current_working_dir) From 35c9e40105f4fc4c8a7d782dc111764d82398cd7 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 24 Jul 2017 17:06:48 -0700 Subject: [PATCH 15/53] debug --- test/functional/setup/setup_dbs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index 46dc5921..f3ff826c 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -89,7 +89,7 @@ if __name__ == '__main__': # populate these tables populateTables(conn_options, args.DBNAME) # setup AE (certificate, column master key and column encryption key) - setupAE(conn_options args.DBNAME) + setupAE(conn_options, args.DBNAME) os.chdir(current_working_dir) From fad1aeb1437932ff7882cd1e0603d83c6511c681 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 24 Jul 2017 17:09:33 -0700 Subject: [PATCH 16/53] debug --- test/functional/setup/setup_dbs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index f3ff826c..e2233f48 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -54,7 +54,7 @@ def setupAE( conn_options, dbname ): inst_command = "certutil -user -p '' -importPFX My " + cert_name + " NoRoot" print("**********In Windows**********") print(inst_command) - executeCommand(inst_command) + executeCommmand(inst_command) # create Column Master Key and Column Encryption Key executeSQLscript('ae_keys.sql', conn_options, dbname) From 62d95dc5bf4ff4d46a802a2121b379e1a446ae8a Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 24 Jul 2017 17:29:25 -0700 Subject: [PATCH 17/53] debug --- test/functional/setup/setup_dbs.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index e2233f48..3cef7185 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -49,9 +49,10 @@ def setupAE( conn_options, dbname ): print("**********In setupAE**********") if platform.system() == 'Windows': # import self signed certificate - dir_name = os.path.realpath(__file__) - cert_name = os.path.join(dir_name, "PHPcert.pfx") - inst_command = "certutil -user -p '' -importPFX My " + cert_name + " NoRoot" + #dir_name = os.path.realpath(__file__) + #cert_name = os.path.join(dir_name, "PHPcert.pfx") + #inst_command = "certutil -user -p '' -importPFX My " + cert_name + " NoRoot" + inst_command = "certutil -user -p '' -importPFX My PHPcert.pfx NoRoot" print("**********In Windows**********") print(inst_command) executeCommmand(inst_command) From ae2ca8ee72fbc6c677f024199ea3261c80c58aa0 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 24 Jul 2017 17:40:01 -0700 Subject: [PATCH 18/53] clean up --- test/functional/setup/setup_dbs.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index 3cef7185..2b8f7208 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -46,15 +46,9 @@ def executeBulkCopy(conn_options, dbname, tblname, datafile): executeCommmand(inst_command) def setupAE( conn_options, dbname ): - print("**********In setupAE**********") if platform.system() == 'Windows': # import self signed certificate - #dir_name = os.path.realpath(__file__) - #cert_name = os.path.join(dir_name, "PHPcert.pfx") - #inst_command = "certutil -user -p '' -importPFX My " + cert_name + " NoRoot" inst_command = "certutil -user -p '' -importPFX My PHPcert.pfx NoRoot" - print("**********In Windows**********") - print(inst_command) executeCommmand(inst_command) # create Column Master Key and Column Encryption Key executeSQLscript('ae_keys.sql', conn_options, dbname) From c8c7bfad57d7f79adb96c682ddd0f779ac30b2ed Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Tue, 25 Jul 2017 11:01:00 -0700 Subject: [PATCH 19/53] merge ae code --- source/pdo_sqlsrv/pdo_dbh.cpp | 10 +++ source/shared/core_sqlsrv.h | 36 +++++++- source/sqlsrv/conn.cpp | 10 +++ .../pdo_sqlsrv/pdo_connect_encrypted.phpt | 69 +++++++++++++++ .../sqlsrv/sqlsrv_connect_encrypted.phpt | 87 +++++++++++++++++++ 5 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index da2bc727..6a35dd03 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -42,6 +42,7 @@ const char ApplicationIntent[] = "ApplicationIntent"; const char AttachDBFileName[] = "AttachDbFileName"; const char ConnectionPooling[] = "ConnectionPooling"; const char Authentication[] = "Authentication"; +const char ColumnEncryption[] = "ColumnEncryption"; #ifdef _WIN32 const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; @@ -220,6 +221,15 @@ const connection_option PDO_CONN_OPTS[] = { CONN_ATTR_BOOL, conn_null_func::func }, + { + PDOConnOptionNames::ColumnEncryption, + sizeof(PDOConnOptionNames::ColumnEncryption), + SQLSRV_CONN_OPTION_COLUMNENCRYPTION, + ODBCConnOptions::ColumnEncryption, + sizeof(ODBCConnOptions::ColumnEncryption), + CONN_ATTR_STRING, + column_encryption_set_func::func + }, #ifdef _WIN32 { PDOConnOptionNames::ConnectRetryCount, diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 6f904f72..491a96fc 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1044,6 +1044,16 @@ enum DRIVER_VERSION : size_t { struct sqlsrv_stmt; struct stmt_option; +// This holds the various details of column encryption. +struct col_encryption_option { + bool enabled; // column encryption enabled, false by default + size_t key_size; // the length of ksp_encrypt_key without the NULL terminator + + col_encryption_option() : enabled(false), key_size(0) + { + } +}; + // *** connection resource structure *** // this is the resource structure returned when a connection is made. struct sqlsrv_conn : public sqlsrv_context { @@ -1051,7 +1061,9 @@ struct sqlsrv_conn : public sqlsrv_context { // instance variables SERVER_VERSION server_version; // version of the server that we're connected to - DRIVER_VERSION driver_version; + DRIVER_VERSION driver_version; + + col_encryption_option ce_option; // holds the details of what are required to enable column encryption // initialize with default values sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding TSRMLS_DC ) : @@ -1085,6 +1097,7 @@ const char APP[] = "APP"; const char ApplicationIntent[] = "ApplicationIntent"; const char AttachDBFileName[] = "AttachDbFileName"; const char Authentication[] = "Authentication"; +const char ColumnEncryption[] = "ColumnEncryption"; const char CharacterSet[] = "CharacterSet"; const char ConnectionPooling[] = "ConnectionPooling"; #ifdef _WIN32 @@ -1131,6 +1144,7 @@ enum SQLSRV_CONN_OPTIONS { SQLSRV_CONN_OPTION_APPLICATION_INTENT, SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, SQLSRV_CONN_OPTION_AUTHENTICATION, + SQLSRV_CONN_OPTION_COLUMNENCRYPTION, SQLSRV_CONN_OPTION_TRANSPARANT_NETWORK_IP_RESOLUTION, #ifdef _WIN32 SQLSRV_CONN_OPTION_CONN_RETRY_COUNT, @@ -2364,4 +2378,24 @@ struct str_conn_attr_func { } }; +struct column_encryption_set_func { + + static void func(connection_option const* option, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC) + { + convert_to_string(value); + const char* value_str = Z_STRVAL_P(value); + + // Column Encryption is disabled by default unless it is explicitly 'Enabled' + conn->ce_option.enabled = false; + if (!stricmp(value_str, "enabled")) { + conn->ce_option.enabled = true; + } + + conn_str += option->odbc_name; + conn_str += "="; + conn_str += value_str; + conn_str += ";"; + } +}; + #endif // CORE_SQLSRV_H diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 8da98f30..f4341fbe 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -188,6 +188,7 @@ const char AttachDBFileName[] = "AttachDbFileName"; const char CharacterSet[] = "CharacterSet"; const char Authentication[] = "Authentication"; const char ConnectionPooling[] = "ConnectionPooling"; +const char ColumnEncryption[] = "ColumnEncryption"; #ifdef _WIN32 const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; @@ -302,6 +303,15 @@ const connection_option SS_CONN_OPTS[] = { CONN_ATTR_BOOL, conn_null_func::func }, + { + SSConnOptionNames::ColumnEncryption, + sizeof(SSConnOptionNames::ColumnEncryption), + SQLSRV_CONN_OPTION_COLUMNENCRYPTION, + ODBCConnOptions::ColumnEncryption, + sizeof(ODBCConnOptions::ColumnEncryption), + CONN_ATTR_STRING, + column_encryption_set_func::func + }, #ifdef _WIN32 { SSConnOptionNames::ConnectRetryCount, diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt new file mode 100644 index 00000000..1416b211 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt @@ -0,0 +1,69 @@ +--TEST-- +Test new connection keyword ColumnEncryption +--SKIPIF-- + +--FILE-- +getMessage() ); + echo "\n"; + } + $conn = null; + //////////////////////////////////////// + $connectionInfo = "Database = $databaseName; ColumnEncryption = false;"; + try + { + $conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd ); + } + catch( PDOException $e ) + { + echo "Failed to connect.\n"; + print_r( $e->getMessage() ); + echo "\n"; + } + //////////////////////////////////////// + $connectionInfo = "Database = $databaseName; ColumnEncryption = 1;"; + try + { + $conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd ); + } + catch( PDOException $e ) + { + echo "Failed to connect.\n"; + print_r( $e->getMessage() ); + echo "\n"; + } + + //////////////////////////////////////// + $connectionInfo = "Database = $databaseName; ColumnEncryption = Disabled;"; + try + { + $conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd ); + echo "Connected successfully with ColumnEncryption disabled.\n"; + } + catch( PDOException $e ) + { + echo "Failed to connect with ColumnEncryption disabled.\n"; + print_r( $e->getMessage() ); + echo "\n"; + } + $conn = null; + echo "Done\n"; +?> +--EXPECTREGEX-- +Connected successfully with ColumnEncryption enabled. +Failed to connect. +SQLSTATE\[08001\]: .*\[Microsoft\]\[ODBC Driver 13 for SQL Server\]Invalid value specified for connection string attribute 'ColumnEncryption' +Failed to connect. +SQLSTATE\[08001\]: .*\[Microsoft\]\[ODBC Driver 13 for SQL Server\]Invalid value specified for connection string attribute 'ColumnEncryption' +Connected successfully with ColumnEncryption disabled. +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt new file mode 100644 index 00000000..4d6c9bac --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt @@ -0,0 +1,87 @@ +--TEST-- +Test new connection keyword ColumnEncryption +--SKIPIF-- + +--FILE-- +$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>'Enabled'); + $conn = sqlsrv_connect( $server, $connectionInfo ); + if( $conn === false ) + { + echo "Failed to connect.\n"; + print_r( sqlsrv_errors() ); + } + else + { + echo "Connected successfully with ColumnEncryption enabled.\n"; + sqlsrv_close( $conn ); + } + + //////////////////////////////////////// + $connectionInfo['ColumnEncryption']='false'; + $conn = sqlsrv_connect( $server, $connectionInfo ); + if( $conn === false ) + { + echo "Failed to connect.\n"; + print_r( sqlsrv_errors() ); + } + //////////////////////////////////////// + $connectionInfo['ColumnEncryption']=true; + $conn = sqlsrv_connect( $server, $connectionInfo ); + if( $conn === false ) + { + echo "Failed to connect.\n"; + print_r( sqlsrv_errors() ); + } + //////////////////////////////////////// + $connectionInfo['ColumnEncryption']='Disabled'; + $conn = sqlsrv_connect( $server, $connectionInfo ); + if( $conn === false ) + { + echo "Failed to connect.\n"; + print_r( sqlsrv_errors() ); + } + else + { + echo "Connected successfully with ColumnEncryption disabled.\n"; + sqlsrv_close( $conn ); + } + + echo "Done\n"; +?> +--EXPECTREGEX-- +Connected successfully with ColumnEncryption enabled. +Failed to connect. +Array +\( + \[0\] => Array + \( + \[0\] => 08001 + \[SQLSTATE\] => 08001 + \[1\] => 0 + \[code\] => 0 + \[2\] => .*\[Microsoft\]\[ODBC Driver 13 for SQL Server\]Invalid value specified for connection string attribute 'ColumnEncryption' + \[message\] => .*\[Microsoft\]\[ODBC Driver 13 for SQL Server\]Invalid value specified for connection string attribute 'ColumnEncryption' + \) + +\) +Failed to connect. +Array +\( + \[0\] => Array + \( + \[0\] => IMSSP + \[SQLSTATE\] => IMSSP + \[1\] => -33 + \[code\] => -33 + \[2\] => Invalid value type for option ColumnEncryption was specified. String type was expected. + \[message\] => Invalid value type for option ColumnEncryption was specified. String type was expected. + \) + +\) +Connected successfully with ColumnEncryption disabled. +Done \ No newline at end of file From 655cd218b1e8f22c12a8d0a6e537c48ad856ab72 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Tue, 25 Jul 2017 11:06:02 -0700 Subject: [PATCH 20/53] revert some changes in mscommon --- test/functional/sqlsrv/MsCommon.inc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/functional/sqlsrv/MsCommon.inc b/test/functional/sqlsrv/MsCommon.inc index 0bc0800e..be9ce1e1 100644 --- a/test/functional/sqlsrv/MsCommon.inc +++ b/test/functional/sqlsrv/MsCommon.inc @@ -175,7 +175,7 @@ function GetTempTableName($table = '', $temporary = true) // dropped once the connection is closed. Otherwise, the caller // should take care of dropping the temp table afterwards. - $timestamp = round(microtime(true)*1000); + $someNumber = rand(0, 1000); $prefix = ''; if ($temporary) @@ -184,7 +184,7 @@ function GetTempTableName($table = '', $temporary = true) if (strlen($table) == 0) $table = 'php_test_table'; - return $prefix . $table . '_' . $timestamp; + return $prefix . $table . '_' . $someNumber; } function GetTempProcName($proc = '', $temporary = true) @@ -193,7 +193,7 @@ function GetTempProcName($proc = '', $temporary = true) // automatically dropped once the connection is closed. Otherwise, // the caller should take care of dropping the temp procedure afterwards. - $timestamp = round(microtime(true)*1000); + $someNumber = rand(0, 1000); $prefix = ''; if ($temporary) @@ -202,7 +202,7 @@ function GetTempProcName($proc = '', $temporary = true) if (strlen($proc) == 0) $proc = 'php_test_proc'; - return $prefix . $proc . '_' . $timestamp; + return $prefix . $proc . '_' . $someNumber; } function ExecuteQuery($conn, $query) From a37fe18fbb5f8b567548d259f1016b270dd5556f Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Tue, 25 Jul 2017 11:09:49 -0700 Subject: [PATCH 21/53] remove encrypttable.ps1 --- test/functional/sqlsrv/encrypttable.ps1 | 45 ------------------------- 1 file changed, 45 deletions(-) delete mode 100644 test/functional/sqlsrv/encrypttable.ps1 diff --git a/test/functional/sqlsrv/encrypttable.ps1 b/test/functional/sqlsrv/encrypttable.ps1 deleted file mode 100644 index 0e14ce8e..00000000 --- a/test/functional/sqlsrv/encrypttable.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -Param( - [Parameter(Mandatory=$True,Position=1)] - [string]$serverName, - [Parameter(Mandatory=$True,Position=2)] - [string]$databaseName, - [Parameter(Mandatory=$True,Position=3)] - [string]$userName, - [Parameter(Mandatory=$True,Position=4)] - [string]$password, - [Parameter(Mandatory=$True,Position=5)] - [string]$tableName, - [Parameter(Mandatory=$True,Position=6)] - [string]$columnNames) - -# Import the SqlServer module. -Import-Module "SqlServer" - -Write-Host $columnNames - -#For SQL Server Authentication -Add-Type -AssemblyName "Microsoft.SqlServer.Smo" -$MySQL = new-object('Microsoft.SqlServer.Management.Smo.Server') $serverName -$MySQL.ConnectionContext.LoginSecure = $false -$MySQL.ConnectionContext.set_Login($userName) -$MySQL.ConnectionContext.set_Password($password) -$database = $MySQL.Databases[$databaseName] - -#split the column names into an array -$column_arr = $columnNames.Split(",") - -# Encrypt the selected columns (or re-encrypt, if they are already encrypted using keys/encrypt types, different than the specified keys/types. -$ces = @() -foreach($col_name in $column_arr){ - $col_full_name = "$tableName.$col_name" - if($col_name -like '*det*'){ - $ces += New-SqlColumnEncryptionSettings -ColumnName $col_full_name -EncryptionType "Deterministic" -EncryptionKey "CEK1" - } - elseif($col_name -like '*rand*'){ - $ces += New-SqlColumnEncryptionSettings -ColumnName $col_full_name -EncryptionType "Randomized" -EncryptionKey "CEK1" - } -} -Set-SqlColumnEncryption -InputObject $database -ColumnEncryptionSettings $ces - -# Disconnect -$MySQL.ConnectionContext.Disconnect() \ No newline at end of file From 36dbfd486d0b2fa64d5b4d39da87b27800c9a5f8 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Tue, 25 Jul 2017 11:43:54 -0700 Subject: [PATCH 22/53] added ae keys setup test for pdo --- test/functional/pdo_sqlsrv/MsCommon.inc | 10 ++++ .../pdo_sqlsrv/test_ae_keys_setup.phpt | 46 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt diff --git a/test/functional/pdo_sqlsrv/MsCommon.inc b/test/functional/pdo_sqlsrv/MsCommon.inc index d1ec0c43..6cd70b8e 100644 --- a/test/functional/pdo_sqlsrv/MsCommon.inc +++ b/test/functional/pdo_sqlsrv/MsCommon.inc @@ -14,6 +14,16 @@ // require 'MsData.inc'; +function IsAEQualified($conn) +{ + $msodbcsql_ver = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; + $server_ver = $conn->getAttribute(PDO::ATTR_SERVER_VERSION); + $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; + if ($msodbcsql_maj < 13 || explode('.', $server_ver)[0] < 13) + return false; + return true; +} + function connect($options=array()) { try diff --git a/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt b/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt new file mode 100644 index 00000000..9c39957a --- /dev/null +++ b/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt @@ -0,0 +1,46 @@ +--TEST-- +retrieval of names of column master key and column encryption key generated in the database setup +--SKIPIF-- + +--FILE-- +query($query); + $master_key_row = $stmt->fetch(); + + $query = "SELECT name FROM sys.column_encryption_keys"; + $stmt = $conn->query($query); + $encryption_key_row = $stmt->fetch(); + + if ($cert_exists && $master_key_row[0] == 'AEMasterKey' && $encryption_key_row[0] == 'AEColumnKey'){ + echo "Test Successfully.\n"; + } + else { + die("Column Master Key and Column Encryption Key not created.\n"); + } + unset($stmt); +} +else { + echo "Test Successfully.\n"; +} +unset($conn); +?> +--EXPECT-- +Test Successfully. \ No newline at end of file From 69170e1531912e7428aae1fe2bbd2c493db86371 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Tue, 25 Jul 2017 13:21:32 -0700 Subject: [PATCH 23/53] fix ae setup test --- test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt | 12 +----------- test/functional/sqlsrv/test_ae_keys_setup.phpt | 14 ++------------ 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt b/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt index 9c39957a..eacfc020 100644 --- a/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt +++ b/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt @@ -11,16 +11,6 @@ require( 'MsCommon.inc' ); $conn = Connect(); if (IsAEQualified($conn)){ - $verify_cert = shell_exec('certutil -user -verifyStore My 237F94738E7F5214D8588006C2269DBC6B370816'); - $cert_exists = false; - if (strpos($verify_cert, 'successfully') != false) - { - $cert_exists = true; - } - else { - die("Certificate does not exist.\n"); - } - $query = "SELECT name FROM sys.column_master_keys"; $stmt = $conn->query($query); $master_key_row = $stmt->fetch(); @@ -29,7 +19,7 @@ if (IsAEQualified($conn)){ $stmt = $conn->query($query); $encryption_key_row = $stmt->fetch(); - if ($cert_exists && $master_key_row[0] == 'AEMasterKey' && $encryption_key_row[0] == 'AEColumnKey'){ + if ($master_key_row[0] == 'AEMasterKey' && $encryption_key_row[0] == 'AEColumnKey'){ echo "Test Successfully.\n"; } else { diff --git a/test/functional/sqlsrv/test_ae_keys_setup.phpt b/test/functional/sqlsrv/test_ae_keys_setup.phpt index 43f8d667..553308ac 100644 --- a/test/functional/sqlsrv/test_ae_keys_setup.phpt +++ b/test/functional/sqlsrv/test_ae_keys_setup.phpt @@ -11,16 +11,6 @@ require( 'MsCommon.inc' ); $conn = Connect(); if (IsAEQualified($conn)){ - $verify_cert = shell_exec('certutil -user -verifyStore My 237F94738E7F5214D8588006C2269DBC6B370816'); - $cert_exists = false; - if (strpos($verify_cert, 'successfully') != false) - { - $cert_exists = true; - } - else { - die("Certificate does not exist.\n"); - } - $query = "SELECT name FROM sys.column_master_keys"; $stmt = sqlsrv_query($conn, $query); sqlsrv_fetch($stmt); @@ -31,11 +21,11 @@ if (IsAEQualified($conn)){ sqlsrv_fetch($stmt); $encryption_key_name = sqlsrv_get_field($stmt, 0); - if ($cert_exists && $master_key_name == 'AEMasterKey' && $encryption_key_name == 'AEColumnKey'){ + if ($master_key_name == 'AEMasterKey' && $encryption_key_name == 'AEColumnKey'){ echo "Test Successfully.\n"; } else { - die("Column Master Key and Column Encryption Key not created.\n"); + echo "Column Master Key and Column Encryption Key not created.\n"; } sqlsrv_free_stmt($stmt); } From c206908def08f219317dd157492decb1852b3023 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Wed, 26 Jul 2017 12:45:58 -0700 Subject: [PATCH 24/53] reply reviews from Jenny --- test/functional/pdo_sqlsrv/MsCommon.inc | 3 ++- test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt | 8 ++++---- test/functional/setup/setup_dbs.py | 6 +++--- test/functional/sqlsrv/MsCommon.inc | 3 ++- test/functional/sqlsrv/test_ae_keys_setup.phpt | 8 ++++---- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/test/functional/pdo_sqlsrv/MsCommon.inc b/test/functional/pdo_sqlsrv/MsCommon.inc index 6cd70b8e..f82e806d 100644 --- a/test/functional/pdo_sqlsrv/MsCommon.inc +++ b/test/functional/pdo_sqlsrv/MsCommon.inc @@ -19,7 +19,8 @@ function IsAEQualified($conn) $msodbcsql_ver = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; $server_ver = $conn->getAttribute(PDO::ATTR_SERVER_VERSION); $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; - if ($msodbcsql_maj < 13 || explode('.', $server_ver)[0] < 13) + $msodbcsql_min = explode(".", $msodbcsql_ver)[1]; + if ($msodbcsql_maj < 13 || ($msodbcsql_maj == 13 && $msodbcsql_min == 0) || explode('.', $server_ver)[0] < 13) return false; return true; } diff --git a/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt b/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt index eacfc020..ed42f74c 100644 --- a/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt +++ b/test/functional/pdo_sqlsrv/test_ae_keys_setup.phpt @@ -1,7 +1,7 @@ --TEST-- retrieval of names of column master key and column encryption key generated in the database setup --SKIPIF-- - + --FILE-- fetch(); if ($master_key_row[0] == 'AEMasterKey' && $encryption_key_row[0] == 'AEColumnKey'){ - echo "Test Successfully.\n"; + echo "Test Successfully done.\n"; } else { die("Column Master Key and Column Encryption Key not created.\n"); @@ -28,9 +28,9 @@ if (IsAEQualified($conn)){ unset($stmt); } else { - echo "Test Successfully.\n"; + echo "Test Successfully done.\n"; } unset($conn); ?> --EXPECT-- -Test Successfully. \ No newline at end of file +Test Successfully done. \ No newline at end of file diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index 2b8f7208..5c262d4b 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -45,8 +45,8 @@ def executeBulkCopy(conn_options, dbname, tblname, datafile): inst_command = redirect_string.format(dbname, tblname, datafile) + conn_options executeCommmand(inst_command) -def setupAE( conn_options, dbname ): - if platform.system() == 'Windows': +def setupAE( conn_options, dbname, azure ): + if (platform.system() == 'Windows' and azure.lower() == 'no'): # import self signed certificate inst_command = "certutil -user -p '' -importPFX My PHPcert.pfx NoRoot" executeCommmand(inst_command) @@ -84,7 +84,7 @@ if __name__ == '__main__': # populate these tables populateTables(conn_options, args.DBNAME) # setup AE (certificate, column master key and column encryption key) - setupAE(conn_options, args.DBNAME) + setupAE(conn_options, args.DBNAME, azure) os.chdir(current_working_dir) diff --git a/test/functional/sqlsrv/MsCommon.inc b/test/functional/sqlsrv/MsCommon.inc index be9ce1e1..bdb53e3d 100644 --- a/test/functional/sqlsrv/MsCommon.inc +++ b/test/functional/sqlsrv/MsCommon.inc @@ -85,7 +85,8 @@ function IsAEQualified($conn) $msodbcsql_ver = sqlsrv_client_info($conn)['DriverVer']; $server_ver = sqlsrv_server_info($conn)['SQLServerVersion']; $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; - if ($msodbcsql_maj < 13 || explode('.', $server_ver)[0] < 13) + $msodbcsql_min = explode(".", $msodbcsql_ver)[1]; + if ($msodbcsql_maj < 13 || ($msodbcsql_maj == 13 && $msodbcsql_min == 0) || explode('.', $server_ver)[0] < 13) return false; return true; } diff --git a/test/functional/sqlsrv/test_ae_keys_setup.phpt b/test/functional/sqlsrv/test_ae_keys_setup.phpt index 553308ac..a54a4569 100644 --- a/test/functional/sqlsrv/test_ae_keys_setup.phpt +++ b/test/functional/sqlsrv/test_ae_keys_setup.phpt @@ -1,7 +1,7 @@ --TEST-- retrieval of names of column master key and column encryption key generated in the database setup --SKIPIF-- - + --FILE-- --EXPECT-- -Test Successfully. \ No newline at end of file +Test Successfully done. \ No newline at end of file From c1f7953e5aa9514cce93d40eb59e9a9b0bd28745 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Wed, 26 Jul 2017 12:51:57 -0700 Subject: [PATCH 25/53] fix setup_dbs.py --- test/functional/setup/setup_dbs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index 5c262d4b..3c8fb404 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -84,7 +84,7 @@ if __name__ == '__main__': # populate these tables populateTables(conn_options, args.DBNAME) # setup AE (certificate, column master key and column encryption key) - setupAE(conn_options, args.DBNAME, azure) + setupAE(conn_options, args.DBNAME, args.AZURE) os.chdir(current_working_dir) From e31c93cffcb2f33f808d96f1187876eb0fa49cb1 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Wed, 26 Jul 2017 14:28:35 -0700 Subject: [PATCH 26/53] change odbc version back to 13 --- test/functional/pdo_sqlsrv/MsCommon.inc | 2 +- test/functional/sqlsrv/MsCommon.inc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/pdo_sqlsrv/MsCommon.inc b/test/functional/pdo_sqlsrv/MsCommon.inc index f82e806d..e8ab0bb1 100644 --- a/test/functional/pdo_sqlsrv/MsCommon.inc +++ b/test/functional/pdo_sqlsrv/MsCommon.inc @@ -20,7 +20,7 @@ function IsAEQualified($conn) $server_ver = $conn->getAttribute(PDO::ATTR_SERVER_VERSION); $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; $msodbcsql_min = explode(".", $msodbcsql_ver)[1]; - if ($msodbcsql_maj < 13 || ($msodbcsql_maj == 13 && $msodbcsql_min == 0) || explode('.', $server_ver)[0] < 13) + if ($msodbcsql_maj < 13 || explode('.', $server_ver)[0] < 13) return false; return true; } diff --git a/test/functional/sqlsrv/MsCommon.inc b/test/functional/sqlsrv/MsCommon.inc index bdb53e3d..204e0fb9 100644 --- a/test/functional/sqlsrv/MsCommon.inc +++ b/test/functional/sqlsrv/MsCommon.inc @@ -86,7 +86,7 @@ function IsAEQualified($conn) $server_ver = sqlsrv_server_info($conn)['SQLServerVersion']; $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; $msodbcsql_min = explode(".", $msodbcsql_ver)[1]; - if ($msodbcsql_maj < 13 || ($msodbcsql_maj == 13 && $msodbcsql_min == 0) || explode('.', $server_ver)[0] < 13) + if ($msodbcsql_maj < 13 || explode('.', $server_ver)[0] < 13) return false; return true; } From 22b4817f658a16edb8d24eba7eba2b2e44e2b01c Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Tue, 8 Aug 2017 12:03:51 -0700 Subject: [PATCH 27/53] Implementation of fetching AE max columns --- source/shared/core_results.cpp | 24 ++++++--- source/shared/core_sqlsrv.h | 18 +++++++ source/shared/core_stmt.cpp | 99 ++++++++++++++++++++++++++-------- 3 files changed, 113 insertions(+), 28 deletions(-) diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index e28cbbed..b9af9075 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -83,7 +83,7 @@ bool get_bit( _In_ void* ptr, _In_ unsigned int bit ) // read in LOB field during buffered result creation SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_buffered_result_set::meta_data& meta, - _In_ zend_long mem_used TSRMLS_DC ); + _In_ zend_long mem_used, _In_ size_t row_count TSRMLS_DC ); // dtor for each row in the cache void cache_row_dtor( _In_ zval* data ); @@ -687,7 +687,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm out_buffer_length = &out_buffer_temp; SQLPOINTER* lob_addr = reinterpret_cast( &row[ meta[i].offset ] ); - *lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC ); + *lob_addr = read_lob_field( stmt, i, meta[i], mem_used, row_count TSRMLS_CC ); // a NULL pointer means NULL field if( *lob_addr == NULL ) { *out_buffer_length = SQL_NULL_DATA; @@ -734,12 +734,12 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm break; } - row_count++; if( *out_buffer_length == SQL_NULL_DATA ) { set_bit( row, i ); } } + row_count++; SQLSRV_ASSERT( row_count < INT_MAX, "Hard maximum of 2 billion rows exceeded in a buffered query" ); // add it to the cache @@ -1498,7 +1498,7 @@ void cache_row_dtor( _In_ zval* data ) } SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_buffered_result_set::meta_data& meta, - _In_ zend_long mem_used TSRMLS_DC ) + _In_ zend_long mem_used, _In_ size_t row_count TSRMLS_DC ) { SQLSMALLINT extra = 0; SQLULEN* output_buffer_len = NULL; @@ -1563,6 +1563,19 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in SQLSRV_ASSERT( SQL_SUCCEEDED( r ), "Unknown SQL error not triggered" ); + if ( stmt->conn->ce_option.enabled == true ) { + // cursor type SQLSRV_CURSOR_BUFFERED has to be FORWARD_ONLY + // thus has to close and reopen cursor to reset the cursor buffer + core::SQLCloseCursor(stmt); + core::SQLExecute(stmt); + // FETCH_NEXT until the cursor reaches the row that it was at + for (int i = 0; i <= row_count; i++) { + core::SQLFetchScroll(stmt, SQL_FETCH_NEXT, 0); + } + } + else { + already_read += to_read - already_read; + } // if the type of the field returns the total to be read, we use that and preallocate the buffer if( last_field_len != SQL_NO_TOTAL ) { @@ -1571,8 +1584,6 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in throw core::CoreException(); } - - already_read += to_read - already_read; to_read = last_field_len; buffer.resize( to_read + extra + sizeof( SQLULEN )); output_buffer_len = reinterpret_cast( buffer.get() ); @@ -1582,7 +1593,6 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in } // otherwise allocate another chunk of memory to read in else { - already_read += to_read - already_read; to_read *= 2; CHECK_CUSTOM_ERROR( mem_used + to_read > stmt->buffered_query_limit * 1024, stmt, SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) { diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 491a96fc..52b52093 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1321,6 +1321,7 @@ struct sqlsrv_stmt : public sqlsrv_context { bool past_fetch_end; // Core_sqlsrv_fetch sets this field when the statement goes beyond the last row sqlsrv_result_set* current_results; // Current result set SQLULEN cursor_type; // Type of cursor for the current result set + int fwd_row_index; // fwd_row_index is the current row index, SQL_CURSOR_FORWARD_ONLY bool has_rows; // Has_rows is set if there are actual rows in the row set bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called int last_field_index; // last field retrieved by core_sqlsrv_get_field @@ -1891,6 +1892,14 @@ namespace core { } } + inline void SQLCloseCursor( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) + { + SQLRETURN r = ::SQLCloseCursor( stmt->handle() ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } inline void SQLColAttribute( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLUSMALLINT field_identifier, _Out_writes_bytes_opt_(buffer_length) SQLPOINTER field_type_char, _In_ SQLSMALLINT buffer_length, @@ -2012,6 +2021,15 @@ namespace core { CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {} } + inline void SQLGetStmtAttr( _Inout_ sqlsrv_stmt* stmt, _In_ SQLINTEGER attr, _Out_writes_opt_(buf_len) void* value_ptr, _In_ SQLINTEGER buf_len, _Out_opt_ SQLINTEGER* str_len TSRMLS_DC) + { + SQLRETURN r; + r = ::SQLGetStmtAttr( stmt->handle(), attr, value_ptr, buf_len, str_len ); + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw CoreException(); + } + } + inline SQLRETURN SQLGetData( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_opt_ SQLLEN* out_buffer_length, _In_ bool handle_warning TSRMLS_DC ) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 54c44fe5..ecf25bbe 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -131,6 +131,7 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error past_fetch_end( false ), current_results( NULL ), cursor_type( SQL_CURSOR_FORWARD_ONLY ), + fwd_row_index( -1 ), has_rows( false ), fetch_called( false ), last_field_index( -1 ), @@ -213,6 +214,7 @@ void sqlsrv_stmt::free_param_data( TSRMLS_D ) void sqlsrv_stmt::new_result_set( TSRMLS_D ) { this->fetch_called = false; + this->fwd_row_index = -1; this->has_rows = false; this->past_next_result_end = false; this->past_fetch_end = false; @@ -796,6 +798,9 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient // move to the record requested. For absolute records, we use a 0 based offset, so +1 since // SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided. SQLRETURN r = stmt->current_results->fetch( fetch_orientation, ( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 TSRMLS_CC ); + if ( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->conn->ce_option.enabled == true ) { + stmt->fwd_row_index++; + } if( r == SQL_NO_DATA ) { // if this is a forward only cursor, mark that we've passed the end so future calls result in an error if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { @@ -2171,6 +2176,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind else { // Get the SQL type of the field. unixODBC 2.3.1 requires wide calls to support pooling core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); + SQLLEN sql_field_len = 0; // Calculate the field size. calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); @@ -2183,8 +2189,9 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind // if this is a large type, then read the first few bytes to get the actual length from SQLGetData if( sql_display_size == 0 || sql_display_size == INT_MAX || sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { - + field_len_temp = INITIAL_FIELD_STRING_LEN; + SQLLEN initiallen = field_len_temp + extra; @@ -2234,19 +2241,48 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - field_len_temp -= initial_field_len; + // only handled differently when AE is on because AE does not support streaming + // SQLSRV_CURSOR_BUFFERED already fetched everything beforehand, so doesn't need to be handled differently here + if ( stmt->conn->ce_option.enabled == true && stmt->current_results->odbc->cursor_type != SQLSRV_CURSOR_BUFFERED ) { + // if the cursor is forward only, we have no choice but to close the cursor and open it again + // but if not forward only, we can simply fetch next then fetch prior to reset the cursor + if ( stmt->current_results->odbc->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { + // reopen the cursor + core::SQLCloseCursor( stmt->current_results->odbc ); + core::SQLExecute( stmt ); + // FETCH_NEXT until the cursor reaches the row that it was at + for ( int i = 0; i <= stmt->fwd_row_index; i++ ) { + core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_NEXT, 0 ); + } + } + else { + core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_NEXT, 0 ); + core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_PRIOR, 0 ); + } + // now that the fetch buffer has reset, fetch the original column again with a bigger buffer length + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, field_len_temp + extra, + &dummy_field_len, false /*handle_warning*/ TSRMLS_CC ); + // if field_len_temp was bit enough to hold all data, dummy_field_len contain the actual amount retrieved, + // not SQL_NO_TOTAL + if ( dummy_field_len != SQL_NO_TOTAL ) + field_len_temp = dummy_field_len; + else + field_len_temp += initial_field_len; + } + else { + field_len_temp -= initial_field_len; - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, - field_len_temp + extra, &dummy_field_len, - false /*handle_warning*/ TSRMLS_CC ); - - // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL - // so we calculate the actual length of the string with that. - if( dummy_field_len != SQL_NO_TOTAL ) - field_len_temp += dummy_field_len; - else - field_len_temp += initial_field_len; + // Get the rest of the data. + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, + field_len_temp + extra, &dummy_field_len, + false /*handle_warning*/ TSRMLS_CC ); + // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL + // so we calculate the actual length of the string with that. + if ( dummy_field_len != SQL_NO_TOTAL ) + field_len_temp += dummy_field_len; + else + field_len_temp += initial_field_len; + } if( r == SQL_SUCCESS_WITH_INFO ) { core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len @@ -2260,13 +2296,36 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind // We got the field_len_temp from SQLGetData call. field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - // We have already recieved INITIAL_FIELD_STRING_LEN size data. - field_len_temp -= INITIAL_FIELD_STRING_LEN; + if ( stmt->conn->ce_option.enabled == true && stmt->current_results->odbc->cursor_type != SQLSRV_CURSOR_BUFFERED ) { + // if the cursor is forward only, we have no choice but to close the cursor and open it again + // but if not forward only, we can simply fetch next then fetch prior to reset the cursor + if ( stmt->current_results->odbc->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { + // reopen the cursor + core::SQLCloseCursor( stmt->current_results->odbc ); + core::SQLExecute( stmt ); + // FETCH_NEXT until the cursor reaches the row that it was at + for ( int i = 0; i <= stmt->fwd_row_index; i++ ) { + core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_NEXT, 0 ); + } + } + else { + core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_NEXT, 0 ); + core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_PRIOR, 0 ); + } + // now that the fetch buffer has reset, fetch the original column again with a bigger buffer length + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, field_len_temp + extra, + &dummy_field_len, false /*handle_warning*/ TSRMLS_CC ); + } + else { + // We have already recieved INITIAL_FIELD_STRING_LEN size data. + field_len_temp -= INITIAL_FIELD_STRING_LEN; - // Get the rest of the data. - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, - field_len_temp + extra, &dummy_field_len, - true /*handle_warning*/ TSRMLS_CC ); + // Get the rest of the data. + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN, + field_len_temp + extra, &dummy_field_len, + true /*handle_warning*/ TSRMLS_CC ); + field_len_temp += INITIAL_FIELD_STRING_LEN; + } if( dummy_field_len == SQL_NULL_DATA ) { field_value = NULL; @@ -2277,8 +2336,6 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { throw core::CoreException(); } - - field_len_temp += INITIAL_FIELD_STRING_LEN; } } // if( is_truncation_warning ( state ) ) From 7a5384650b1972651661a1ee810cd5322d2da2c0 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 14 Aug 2017 11:44:54 -0700 Subject: [PATCH 28/53] fixed indentation in core_stmt.cpp --- source/shared/core_stmt.cpp | 326 ++++++++++++++++++------------------ 1 file changed, 162 insertions(+), 164 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index ecf25bbe..d12a490f 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -2135,87 +2135,85 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC ) { - SQLRETURN r; - SQLSMALLINT c_type; - SQLLEN sql_field_type = 0; - SQLSMALLINT extra = 0; - SQLLEN field_len_temp = 0; - SQLLEN sql_display_size = 0; - char* field_value_temp = NULL; + SQLRETURN r; + SQLSMALLINT c_type; + SQLLEN sql_field_type = 0; + SQLSMALLINT extra = 0; + SQLLEN field_len_temp = 0; + SQLLEN sql_display_size = 0; + char* field_value_temp = NULL; - try { + try { - DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, - "Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); + DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, + "Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { - sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); - } + if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { + sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); + } - // Set the C type and account for null characters at the end of the data. - switch( sqlsrv_php_type.typeinfo.encoding ) { - case CP_UTF8: - c_type = SQL_C_WCHAR; - extra = sizeof( SQLWCHAR ); - break; - case SQLSRV_ENCODING_BINARY: - c_type = SQL_C_BINARY; - extra = 0; - break; - default: - c_type = SQL_C_CHAR; - extra = sizeof( SQLCHAR ); - break; - } + // Set the C type and account for null characters at the end of the data. + switch( sqlsrv_php_type.typeinfo.encoding ) { + case CP_UTF8: + c_type = SQL_C_WCHAR; + extra = sizeof( SQLWCHAR ); + break; + case SQLSRV_ENCODING_BINARY: + c_type = SQL_C_BINARY; + extra = 0; + break; + default: + c_type = SQL_C_CHAR; + extra = sizeof( SQLCHAR ); + break; + } - col_cache* cached = NULL; - if ( NULL != ( cached = static_cast< col_cache* >( zend_hash_index_find_ptr( Z_ARRVAL( stmt->col_cache ), static_cast< zend_ulong >( field_index ))))) { - sql_field_type = cached->sql_type; - sql_display_size = cached->display_size; - } - else { - // Get the SQL type of the field. unixODBC 2.3.1 requires wide calls to support pooling - core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); + col_cache* cached = NULL; + if ( NULL != ( cached = static_cast< col_cache* >( zend_hash_index_find_ptr( Z_ARRVAL( stmt->col_cache ), static_cast< zend_ulong >( field_index ))))) { + sql_field_type = cached->sql_type; + sql_display_size = cached->display_size; + } + else { + // Get the SQL type of the field. unixODBC 2.3.1 requires wide calls to support pooling + core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); SQLLEN sql_field_len = 0; - // Calculate the field size. - calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); + // Calculate the field size. + calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); - col_cache cache( sql_field_type, sql_display_size ); - core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->col_cache ), field_index, &cache, sizeof( col_cache ) TSRMLS_CC ); - } + col_cache cache( sql_field_type, sql_display_size ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->col_cache ), field_index, &cache, sizeof( col_cache ) TSRMLS_CC ); + } - - // if this is a large type, then read the first few bytes to get the actual length from SQLGetData - if( sql_display_size == 0 || sql_display_size == INT_MAX || - sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { + // if this is a large type, then read the first few bytes to get the actual length from SQLGetData + if( sql_display_size == 0 || sql_display_size == INT_MAX || + sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { - field_len_temp = INITIAL_FIELD_STRING_LEN; + field_len_temp = INITIAL_FIELD_STRING_LEN; SQLLEN initiallen = field_len_temp + extra; + field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); - field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), + &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), - &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } + if( field_len_temp == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } - if( field_len_temp == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } - - if( r == SQL_SUCCESS_WITH_INFO ) { + if( r == SQL_SUCCESS_WITH_INFO ) { SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = { 0 }; - SQLSMALLINT len = 0; + SQLSMALLINT len = 0; - stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); + stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); // with Linux connection pooling may not get a truncated warning back but the actual field_len_temp // can be greater than the initallen value. @@ -2225,25 +2223,25 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind if( is_truncated_warning( state ) ) { #endif // !_WIN32 - SQLLEN dummy_field_len = 0; + SQLLEN dummy_field_len = 0; - // for XML (and possibly other conditions) the field length returned is not the real field length, so - // in every pass, we double the allocation size to retrieve all the contents. - if( field_len_temp == SQL_NO_TOTAL ) { + // for XML (and possibly other conditions) the field length returned is not the real field length, so + // in every pass, we double the allocation size to retrieve all the contents. + if( field_len_temp == SQL_NO_TOTAL ) { - // reset the field_len_temp - field_len_temp = INITIAL_FIELD_STRING_LEN; + // reset the field_len_temp + field_len_temp = INITIAL_FIELD_STRING_LEN; - do { - SQLLEN initial_field_len = field_len_temp; - // Double the size. - field_len_temp *= 2; + do { + SQLLEN initial_field_len = field_len_temp; + // Double the size. + field_len_temp *= 2; - field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); + field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); // only handled differently when AE is on because AE does not support streaming - // SQLSRV_CURSOR_BUFFERED already fetched everything beforehand, so doesn't need to be handled differently here - if ( stmt->conn->ce_option.enabled == true && stmt->current_results->odbc->cursor_type != SQLSRV_CURSOR_BUFFERED ) { + // SQLSRV_CURSOR_BUFFERED already fetched everything beforehand, so doesn't need to be handled differently here + if ( stmt->conn->ce_option.enabled == true && stmt->current_results->odbc->cursor_type != SQLSRV_CURSOR_BUFFERED ) { // if the cursor is forward only, we have no choice but to close the cursor and open it again // but if not forward only, we can simply fetch next then fetch prior to reset the cursor if ( stmt->current_results->odbc->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { @@ -2284,17 +2282,17 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind field_len_temp += initial_field_len; } - if( r == SQL_SUCCESS_WITH_INFO ) { - core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len - TSRMLS_CC ); - } + if( r == SQL_SUCCESS_WITH_INFO ) { + core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len + TSRMLS_CC ); + } - } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); - } - else { + } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); + } + else { - // We got the field_len_temp from SQLGetData call. - field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); + // We got the field_len_temp from SQLGetData call. + field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); if ( stmt->conn->ce_option.enabled == true && stmt->current_results->odbc->cursor_type != SQLSRV_CURSOR_BUFFERED ) { // if the cursor is forward only, we have no choice but to close the cursor and open it again @@ -2327,93 +2325,93 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind field_len_temp += INITIAL_FIELD_STRING_LEN; } - if( dummy_field_len == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } + if( dummy_field_len == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } - } + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } + } - } // if( is_truncation_warning ( state ) ) - else { - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } - } - } // if( r == SQL_SUCCESS_WITH_INFO ) + } // if( is_truncation_warning ( state ) ) + else { + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { + throw core::CoreException(); + } + } + } // if( r == SQL_SUCCESS_WITH_INFO ) - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) { + if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) { - bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), - &field_value_temp, field_len_temp ); + bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), + &field_value_temp, field_len_temp ); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - } // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. ) + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + } // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. ) - else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) { + else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) { - // only allow binary retrievals for char and binary types. All others get a string converted - // to the encoding type they asked for. + // only allow binary retrievals for char and binary types. All others get a string converted + // to the encoding type they asked for. - // null terminator - if( c_type == SQL_C_CHAR ) { - sql_display_size += sizeof( SQLCHAR ); - } + // null terminator + if( c_type == SQL_C_CHAR ) { + sql_display_size += sizeof( SQLCHAR ); + } - // For WCHAR multiply by sizeof(WCHAR) and include the null terminator - else if( c_type == SQL_C_WCHAR ) { - sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); - } + // For WCHAR multiply by sizeof(WCHAR) and include the null terminator + else if( c_type == SQL_C_WCHAR ) { + sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR); + } - field_value_temp = static_cast( sqlsrv_malloc( sql_display_size + extra + 1 )); + field_value_temp = static_cast( sqlsrv_malloc( sql_display_size + extra + 1 )); - // get the data - r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size, - &field_len_temp, true /*handle_warning*/ TSRMLS_CC ); - CHECK_SQL_ERROR( r, stmt ) { - throw core::CoreException(); - } - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); - } + // get the data + r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size, + &field_len_temp, true /*handle_warning*/ TSRMLS_CC ); + CHECK_SQL_ERROR( r, stmt ) { + throw core::CoreException(); + } + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { + throw core::CoreException(); + } - if( field_len_temp == SQL_NULL_DATA ) { - field_value = NULL; - sqlsrv_free( field_value_temp ); - return; - } + if( field_len_temp == SQL_NULL_DATA ) { + field_value = NULL; + sqlsrv_free( field_value_temp ); + return; + } - if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) { + if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) { - bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), - &field_value_temp, field_len_temp ); + bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), + &field_value_temp, field_len_temp ); - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { - throw core::CoreException(); - } - } - } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) + CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) - else { + else { - DIE( "Invalid sql_display_size" ); - return; // to eliminate a warning - } + DIE( "Invalid sql_display_size" ); + return; // to eliminate a warning + } - field_value = field_value_temp; - *field_len = field_len_temp; +field_value = field_value_temp; +*field_len = field_len_temp; - // prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP - // runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode. - // SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary - // operator to set add 1 to fill the null terminator + // prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP + // runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode. + // SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary + // operator to set add 1 to fill the null terminator // with unixODBC connection pooling sometimes field_len_temp can be SQL_NO_DATA. // In that cause do not set null terminator and set length to 0. @@ -2425,22 +2423,22 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind { *field_len = 0; } - } + } - catch( core::CoreException& ) { + catch( core::CoreException& ) { - field_value = NULL; - *field_len = 0; - sqlsrv_free( field_value_temp ); - throw; - } - catch ( ... ) { + field_value = NULL; + *field_len = 0; + sqlsrv_free( field_value_temp ); + throw; + } + catch ( ... ) { - field_value = NULL; - *field_len = 0; - sqlsrv_free( field_value_temp ); - throw; - } + field_value = NULL; + *field_len = 0; + sqlsrv_free( field_value_temp ); + throw; + } } From 0100910be1d4db4cdd7ab49df7dfade3811d771f Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 14 Aug 2017 17:09:49 -0700 Subject: [PATCH 29/53] refactored AE related code --- source/shared/core_stmt.cpp | 64 ++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index d12a490f..72d297aa 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -112,6 +112,7 @@ bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type ); void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z, _In_ SQLULEN paramno, SQLSRV_ENCODING encoding, _In_ SQLSMALLINT c_type, _In_ SQLSMALLINT sql_type, _In_ SQLULEN column_size, _Out_writes_(buffer_len) SQLPOINTER& buffer, _Out_ SQLLEN& buffer_len TSRMLS_DC ); +bool reset_ae_stream_cursor( _Inout_ sqlsrv_stmt* stmt ); void save_output_param_for_later( _Inout_ sqlsrv_stmt* stmt, _Inout_ sqlsrv_output_param& param TSRMLS_DC ); // send all the stream data void send_param_streams( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); @@ -798,6 +799,10 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient // move to the record requested. For absolute records, we use a 0 based offset, so +1 since // SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided. SQLRETURN r = stmt->current_results->fetch( fetch_orientation, ( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 TSRMLS_CC ); + + // when AE is on, need to store the current row number being fetched so a cursor can be reset back to it's original position when getting stream data + // fwd_row_index is used to store the current row index, which is incremented everytime SQLFetchScroll is called with SQLSRV_CURSOR_FORWARD_ONLY + // fetching encrypted max type only works for SQLSRV_CURSOR_FORWARD_ONLY, thus no need to save this number when CURSOR is non-FORWARD_ONLY if ( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->conn->ce_option.enabled == true ) { stmt->fwd_row_index++; } @@ -2176,7 +2181,6 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind else { // Get the SQL type of the field. unixODBC 2.3.1 requires wide calls to support pooling core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - SQLLEN sql_field_len = 0; // Calculate the field size. calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); @@ -2239,25 +2243,9 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - // only handled differently when AE is on because AE does not support streaming - // SQLSRV_CURSOR_BUFFERED already fetched everything beforehand, so doesn't need to be handled differently here - if ( stmt->conn->ce_option.enabled == true && stmt->current_results->odbc->cursor_type != SQLSRV_CURSOR_BUFFERED ) { - // if the cursor is forward only, we have no choice but to close the cursor and open it again - // but if not forward only, we can simply fetch next then fetch prior to reset the cursor - if ( stmt->current_results->odbc->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { - // reopen the cursor - core::SQLCloseCursor( stmt->current_results->odbc ); - core::SQLExecute( stmt ); - // FETCH_NEXT until the cursor reaches the row that it was at - for ( int i = 0; i <= stmt->fwd_row_index; i++ ) { - core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_NEXT, 0 ); - } - } - else { - core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_NEXT, 0 ); - core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_PRIOR, 0 ); - } - // now that the fetch buffer has reset, fetch the original column again with a bigger buffer length + // reset AE stream fetch buffer + if ( reset_ae_stream_cursor( stmt )){ + // fetch the original column again with a bigger buffer length r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, field_len_temp + extra, &dummy_field_len, false /*handle_warning*/ TSRMLS_CC ); // if field_len_temp was bit enough to hold all data, dummy_field_len contain the actual amount retrieved, @@ -2294,23 +2282,9 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind // We got the field_len_temp from SQLGetData call. field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); - if ( stmt->conn->ce_option.enabled == true && stmt->current_results->odbc->cursor_type != SQLSRV_CURSOR_BUFFERED ) { - // if the cursor is forward only, we have no choice but to close the cursor and open it again - // but if not forward only, we can simply fetch next then fetch prior to reset the cursor - if ( stmt->current_results->odbc->cursor_type == SQL_CURSOR_FORWARD_ONLY ) { - // reopen the cursor - core::SQLCloseCursor( stmt->current_results->odbc ); - core::SQLExecute( stmt ); - // FETCH_NEXT until the cursor reaches the row that it was at - for ( int i = 0; i <= stmt->fwd_row_index; i++ ) { - core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_NEXT, 0 ); - } - } - else { - core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_NEXT, 0 ); - core::SQLFetchScroll( stmt->current_results->odbc, SQL_FETCH_PRIOR, 0 ); - } - // now that the fetch buffer has reset, fetch the original column again with a bigger buffer length + // reset AE stream fetch buffer + if ( reset_ae_stream_cursor( stmt ) ) { + // fetch the original column again with a bigger buffer length r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, field_len_temp + extra, &dummy_field_len, false /*handle_warning*/ TSRMLS_CC ); } @@ -2579,6 +2553,22 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* } } +bool reset_ae_stream_cursor( _Inout_ sqlsrv_stmt* stmt ) { + // only handled differently when AE is on because AE does not support streaming + // AE only works with SQL_CURSOR_FORWARD_ONLY for max types + if (stmt->conn->ce_option.enabled == true && stmt->current_results->odbc->cursor_type == SQL_CURSOR_FORWARD_ONLY) { + // close and reopen the cursor + core::SQLCloseCursor(stmt->current_results->odbc); + core::SQLExecute(stmt); + // FETCH_NEXT until the cursor reaches the row that it was at + for (int i = 0; i <= stmt->fwd_row_index; i++) { + core::SQLFetchScroll(stmt->current_results->odbc, SQL_FETCH_NEXT, 0); + } + return true; + } + return false; +} + // output parameters have their reference count incremented so that they do not disappear // while the query is executed and processed. They are saved in the statement so that // their reference count may be decremented later (after results are processed) From f8816b16de9eea3083ea57d942ec997b0b602381 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Mon, 14 Aug 2017 17:20:11 -0700 Subject: [PATCH 30/53] add comment --- source/shared/core_stmt.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 72d297aa..abd4d9ff 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -2244,7 +2244,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); // reset AE stream fetch buffer - if ( reset_ae_stream_cursor( stmt )){ + if ( reset_ae_stream_cursor( stmt )){ // fetch the original column again with a bigger buffer length r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, field_len_temp + extra, &dummy_field_len, false /*handle_warning*/ TSRMLS_CC ); @@ -2278,8 +2278,8 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); } else { - - // We got the field_len_temp from SQLGetData call. + // the real field length is returned here, thus no need to double the allocation size here, just have to + // allocate field_len_temp (which is the field length retrieved from the first SQLGetData field_value_temp = static_cast( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 )); // reset AE stream fetch buffer From ff671fcde34b402752b9dfe0635bc7e6ad905409 Mon Sep 17 00:00:00 2001 From: v-kaywon Date: Wed, 16 Aug 2017 16:48:09 -0700 Subject: [PATCH 31/53] change comment in core_stmt.cpp --- source/shared/core_stmt.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index abd4d9ff..5e025862 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -800,9 +800,7 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient // SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided. SQLRETURN r = stmt->current_results->fetch( fetch_orientation, ( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 TSRMLS_CC ); - // when AE is on, need to store the current row number being fetched so a cursor can be reset back to it's original position when getting stream data - // fwd_row_index is used to store the current row index, which is incremented everytime SQLFetchScroll is called with SQLSRV_CURSOR_FORWARD_ONLY - // fetching encrypted max type only works for SQLSRV_CURSOR_FORWARD_ONLY, thus no need to save this number when CURSOR is non-FORWARD_ONLY + // when AE is enabled, will keep track of the number of rows being fetched so far such that the cursor can be reset back to its original position when getting stream data if ( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->conn->ce_option.enabled == true ) { stmt->fwd_row_index++; } From a1c5e6eb703b0eb180d3f9bb25fd81743d4d8d44 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 23 Aug 2017 12:36:24 -0700 Subject: [PATCH 32/53] Added support for loading a custom keystore provider for Column Encryption --- source/pdo_sqlsrv/pdo_dbh.cpp | 49 ++++++++++++++++++++----- source/pdo_sqlsrv/pdo_util.cpp | 16 ++++++++ source/shared/core_conn.cpp | 67 +++++++++++++++++++++++++++++++++- source/shared/core_sqlsrv.h | 61 ++++++++++++++++++++++++++++--- source/sqlsrv/conn.cpp | 67 +++++++++++++++++++++++++--------- source/sqlsrv/util.cpp | 17 +++++++++ 6 files changed, 242 insertions(+), 35 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 7e5e3a47..94c9b074 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -43,6 +43,10 @@ const char AttachDBFileName[] = "AttachDbFileName"; const char ConnectionPooling[] = "ConnectionPooling"; const char Authentication[] = "Authentication"; const char ColumnEncryption[] = "ColumnEncryption"; +const char CEKeystoreProvider[] = "CEKeystoreProvider"; +const char CEKeystoreName[] = "CEKeystoreName"; +const char CEKeystoreEncryptKey[] = "CEKeystoreEncryptKey"; + #ifdef _WIN32 const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; @@ -221,15 +225,42 @@ const connection_option PDO_CONN_OPTS[] = { CONN_ATTR_BOOL, conn_null_func::func }, - { - PDOConnOptionNames::ColumnEncryption, - sizeof(PDOConnOptionNames::ColumnEncryption), - SQLSRV_CONN_OPTION_COLUMNENCRYPTION, - ODBCConnOptions::ColumnEncryption, - sizeof(ODBCConnOptions::ColumnEncryption), - CONN_ATTR_STRING, - column_encryption_set_func::func - }, + { + PDOConnOptionNames::ColumnEncryption, + sizeof(PDOConnOptionNames::ColumnEncryption), + SQLSRV_CONN_OPTION_COLUMNENCRYPTION, + ODBCConnOptions::ColumnEncryption, + sizeof(ODBCConnOptions::ColumnEncryption), + CONN_ATTR_STRING, + column_encryption_set_func::func + }, + { + PDOConnOptionNames::CEKeystoreProvider, + sizeof(PDOConnOptionNames::CEKeystoreProvider), + SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER, + ODBCConnOptions::CEKeystoreProvider, + sizeof(ODBCConnOptions::CEKeystoreProvider), + CONN_ATTR_STRING, + ce_ksp_provider_set_func::func + }, + { + PDOConnOptionNames::CEKeystoreName, + sizeof(PDOConnOptionNames::CEKeystoreName), + SQLSRV_CONN_OPTION_CEKEYSTORE_NAME, + ODBCConnOptions::CEKeystoreName, + sizeof(ODBCConnOptions::CEKeystoreName), + CONN_ATTR_STRING, + ce_ksp_provider_set_func::func + }, + { + PDOConnOptionNames::CEKeystoreEncryptKey, + sizeof(PDOConnOptionNames::CEKeystoreEncryptKey), + SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY, + ODBCConnOptions::CEKeystoreEncryptKey, + sizeof(ODBCConnOptions::CEKeystoreEncryptKey), + CONN_ATTR_STRING, + ce_ksp_provider_set_func::func + }, #ifdef _WIN32 { PDOConnOptionNames::ConnectRetryCount, diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 3f08c0b3..6ff8b430 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -381,6 +381,22 @@ pdo_error PDO_ERRORS[] = { PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, { IMSSP, (SQLCHAR*) "Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported.", -73, false } }, + { + SQLSRV_ERROR_KEYSTORE_NAME_MISSING, + { IMSSP, (SQLCHAR*) "The name of the custom keystore provider is missing.", -74, false} + }, + { + SQLSRV_ERROR_KEYSTORE_PATH_MISSING, + { IMSSP, (SQLCHAR*) "The path to the custom keystore provider is missing.", -75, false} + }, + { + SQLSRV_ERROR_KEYSTORE_KEY_MISSING, + { IMSSP, (SQLCHAR*) "The encryption key for the custom keystore provider is missing.", -76, false} + }, + { + SQLSRV_ERROR_KEYSTORE_INVALID_VALUE, + { IMSSP, (SQLCHAR*) "Invalid value for loading a custom keystore provider.", -77, false} + }, { UINT_MAX, {} } }; diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 0cc17a64..eae1d427 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -71,7 +71,7 @@ const char* get_processor_arch( void ); void get_server_version( _Inout_ sqlsrv_conn* conn, _Outptr_result_buffer_(len) char** server_version, _Out_ SQLSMALLINT& len TSRMLS_DC ); connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ const char* key, _In_ SQLULEN key_len TSRMLS_DC ); void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_len) const char* val, _Inout_ size_t val_len, _Inout_ std::string& conn_str TSRMLS_DC ); - +void load_configure_ksp( _Inout_ sqlsrv_conn* conn TSRMLS_DC ); } // core_sqlsrv_connect @@ -207,6 +207,8 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont throw core::CoreException(); } + load_configure_ksp( conn ); + // determine the version of the server we're connected to. The server version is left in the // connection upon return. // @@ -776,7 +778,68 @@ void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) conn->server_version = version_major; } -void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_len) const char* val, _Inout_ size_t val_len, _Inout_ std::string& conn_str TSRMLS_DC ) +// Column Encryption feature: if a custom keystore provider is specified, +// load and configure it when column encryption is enabled, but this step have +// to be executed after the connection has been established +void load_configure_ksp( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) +{ + // If column encryption is not enabled simply do nothing. Otherwise, check if a custom keystore provider + // is required for encryption or decryption. Note, in order to load and configure a custom keystore provider, + // all KSP fields in conn->ce_option must be defined. + if ( ! conn->ce_option.enabled || ! conn->ce_option.ksp_required ) + return; + + // Do something like the following sample + // use the KSP related fields in conn->ce_option + // CEKEYSTOREDATA is defined in msodbcsql.h + // https://docs.microsoft.com/en-us/sql/connect/odbc/custom-keystore-providers + + CHECK_CUSTOM_ERROR( conn->ce_option.ksp_name == NULL, conn, SQLSRV_ERROR_KEYSTORE_NAME_MISSING) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( conn->ce_option.ksp_path == NULL, conn, SQLSRV_ERROR_KEYSTORE_PATH_MISSING) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR( conn->ce_option.key_size == 0, conn, SQLSRV_ERROR_KEYSTORE_KEY_MISSING) { + throw core::CoreException(); + } + + char* ksp_name = Z_STRVAL_P( conn->ce_option.ksp_name ); + char* ksp_path = Z_STRVAL_P( conn->ce_option.ksp_path ); + unsigned int name_len = Z_STRLEN_P( conn->ce_option.ksp_name ); + unsigned int path_len = Z_STRLEN_P( conn->ce_option.ksp_path ); + unsigned int key_size = conn->ce_option.key_size; + + sqlsrv_malloc_auto_ptr ksp_data; + + ksp_data = reinterpret_cast( sqlsrv_malloc( sizeof( CEKEYSTOREDATA ) + key_size ) ); + + CEKEYSTOREDATA *pKsd = (CEKEYSTOREDATA*) ksp_data.get(); + + pKsd->dataSize = key_size; + + // First, convert conn->ce_option.ksp_name to a WCHAR version + unsigned int wname_len = 0; + sqlsrv_malloc_auto_ptr wksp_name; + wksp_name = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, ksp_name, name_len, &wname_len ); + + CHECK_CUSTOM_ERROR( wksp_name == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE ) { + throw core::CoreException(); + } + + pKsd->name = (wchar_t *) wksp_name.get(); + + // Next, extract the character string from conn->ce_option.ksp_encrypt_key into encrypt_key + char* encrypt_key = Z_STRVAL_P( conn->ce_option.ksp_encrypt_key ); + memcpy_s( pKsd->data, key_size * sizeof( char ) , encrypt_key, key_size ); + + core::SQLSetConnectAttr( conn, SQL_COPT_SS_CEKEYSTOREPROVIDER, ksp_path, SQL_NTS ); + core::SQLSetConnectAttr( conn, SQL_COPT_SS_CEKEYSTOREDATA, reinterpret_cast( pKsd ), SQL_IS_POINTER ); +} + +void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ) { // wrap a connection option in a quote. It is presumed that any character that need to be escaped will // be escaped, such as a closing }. diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index dee470e4..6a4c4235 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1046,12 +1046,16 @@ struct stmt_option; // This holds the various details of column encryption. struct col_encryption_option { - bool enabled; // column encryption enabled, false by default - size_t key_size; // the length of ksp_encrypt_key without the NULL terminator + bool enabled; // column encryption enabled, false by default + zval_auto_ptr ksp_name; // keystore provider name + zval_auto_ptr ksp_path; // keystore provider path to the dynamically linked libary (either a *.dll or *.so) + zval_auto_ptr ksp_encrypt_key; // the encryption key used to configure the keystore provider + size_t key_size; // the length of ksp_encrypt_key without the NULL terminator + bool ksp_required; // a keystore provider is required to enable column encryption, false by default - col_encryption_option() : enabled(false), key_size(0) - { - } + col_encryption_option() : enabled( false ), key_size ( 0 ), ksp_required( false ) + { + } }; // *** connection resource structure *** @@ -1098,6 +1102,9 @@ const char ApplicationIntent[] = "ApplicationIntent"; const char AttachDBFileName[] = "AttachDbFileName"; const char Authentication[] = "Authentication"; const char ColumnEncryption[] = "ColumnEncryption"; +const char CEKeystoreProvider[] = "CEKeystoreProvider"; +const char CEKeystoreName[] = "CEKeystoreName"; +const char CEKeystoreEncryptKey[] = "CEKeystoreEncryptKey"; const char CharacterSet[] = "CharacterSet"; const char ConnectionPooling[] = "ConnectionPooling"; #ifdef _WIN32 @@ -1145,6 +1152,9 @@ enum SQLSRV_CONN_OPTIONS { SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER, SQLSRV_CONN_OPTION_AUTHENTICATION, SQLSRV_CONN_OPTION_COLUMNENCRYPTION, + SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER, + SQLSRV_CONN_OPTION_CEKEYSTORE_NAME, + SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY, SQLSRV_CONN_OPTION_TRANSPARANT_NETWORK_IP_RESOLUTION, #ifdef _WIN32 SQLSRV_CONN_OPTION_CONN_RETRY_COUNT, @@ -1646,6 +1656,10 @@ enum SQLSRV_ERROR_CODES { SQLSRV_ERROR_FIELD_INDEX_ERROR, SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, SQLSRV_ERROR_INVALID_BUFFER_LIMIT, + SQLSRV_ERROR_KEYSTORE_NAME_MISSING, + SQLSRV_ERROR_KEYSTORE_PATH_MISSING, + SQLSRV_ERROR_KEYSTORE_KEY_MISSING, + SQLSRV_ERROR_KEYSTORE_INVALID_VALUE, // Driver specific error codes starts from here. SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, @@ -2398,7 +2412,7 @@ struct str_conn_attr_func { struct column_encryption_set_func { - static void func(connection_option const* option, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC) + static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC) { convert_to_string(value); const char* value_str = Z_STRVAL_P(value); @@ -2416,4 +2430,39 @@ struct column_encryption_set_func { } }; +struct ce_ksp_provider_set_func { + + static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC) + { + CHECK_CUSTOM_ERROR( Z_TYPE_P( value ) != IS_STRING, conn, SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, option->odbc_name ) { + throw core::CoreException(); + } + + size_t value_len = Z_STRLEN_P( value ); + + CHECK_CUSTOM_ERROR( value_len == 0, conn, SQLSRV_ERROR_KEYSTORE_INVALID_VALUE ) { + throw core::CoreException(); + } + + switch ( option->conn_option_key ) { + case SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER: + conn->ce_option.ksp_path = value; + conn->ce_option.ksp_required = true; + break; + case SQLSRV_CONN_OPTION_CEKEYSTORE_NAME: + conn->ce_option.ksp_name = value; + conn->ce_option.ksp_required = true; + break; + case SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY: + conn->ce_option.ksp_encrypt_key = value; + conn->ce_option.key_size = value_len; + conn->ce_option.ksp_required = true; + break; + default: + SQLSRV_ASSERT( false, "ce_ksp_provider_set_func: Invalid KSP option!" ); + break; + } + } +}; + #endif // CORE_SQLSRV_H diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index aba5e372..32afc87d 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -189,6 +189,10 @@ const char CharacterSet[] = "CharacterSet"; const char Authentication[] = "Authentication"; const char ConnectionPooling[] = "ConnectionPooling"; const char ColumnEncryption[] = "ColumnEncryption"; +const char CEKeystoreProvider[] = "CEKeystoreProvider"; +const char CEKeystoreName[] = "CEKeystoreName"; +const char CEKeystoreEncryptKey[] = "CEKeystoreEncryptKey"; + #ifdef _WIN32 const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; @@ -294,24 +298,51 @@ const connection_option SS_CONN_OPTS[] = { CONN_ATTR_STRING, conn_str_append_func::func }, - { - SSConnOptionNames::ConnectionPooling, - sizeof( SSConnOptionNames::ConnectionPooling ), - SQLSRV_CONN_OPTION_CONN_POOLING, - ODBCConnOptions::ConnectionPooling, - sizeof( ODBCConnOptions::ConnectionPooling ), - CONN_ATTR_BOOL, - conn_null_func::func - }, - { - SSConnOptionNames::ColumnEncryption, - sizeof(SSConnOptionNames::ColumnEncryption), - SQLSRV_CONN_OPTION_COLUMNENCRYPTION, - ODBCConnOptions::ColumnEncryption, - sizeof(ODBCConnOptions::ColumnEncryption), - CONN_ATTR_STRING, - column_encryption_set_func::func - }, + { + SSConnOptionNames::ConnectionPooling, + sizeof(SSConnOptionNames::ConnectionPooling), + SQLSRV_CONN_OPTION_CONN_POOLING, + ODBCConnOptions::ConnectionPooling, + sizeof(ODBCConnOptions::ConnectionPooling), + CONN_ATTR_BOOL, + conn_null_func::func + }, + { + SSConnOptionNames::ColumnEncryption, + sizeof(SSConnOptionNames::ColumnEncryption), + SQLSRV_CONN_OPTION_COLUMNENCRYPTION, + ODBCConnOptions::ColumnEncryption, + sizeof(ODBCConnOptions::ColumnEncryption), + CONN_ATTR_STRING, + column_encryption_set_func::func + }, + { + SSConnOptionNames::CEKeystoreProvider, + sizeof(SSConnOptionNames::CEKeystoreProvider), + SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER, + ODBCConnOptions::CEKeystoreProvider, + sizeof(ODBCConnOptions::CEKeystoreProvider), + CONN_ATTR_STRING, + ce_ksp_provider_set_func::func + }, + { + SSConnOptionNames::CEKeystoreName, + sizeof(SSConnOptionNames::CEKeystoreName), + SQLSRV_CONN_OPTION_CEKEYSTORE_NAME, + ODBCConnOptions::CEKeystoreName, + sizeof(ODBCConnOptions::CEKeystoreName), + CONN_ATTR_STRING, + ce_ksp_provider_set_func::func + }, + { + SSConnOptionNames::CEKeystoreEncryptKey, + sizeof(SSConnOptionNames::CEKeystoreEncryptKey), + SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY, + ODBCConnOptions::CEKeystoreEncryptKey, + sizeof(ODBCConnOptions::CEKeystoreEncryptKey), + CONN_ATTR_STRING, + ce_ksp_provider_set_func::func + }, #ifdef _WIN32 { SSConnOptionNames::ConnectRetryCount, diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index cd04101c..ba3550db 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -377,6 +377,23 @@ ss_error SS_ERRORS[] = { { SSPWARN, (SQLCHAR*)"An empty field name was skipped by sqlsrv_fetch_object.", -100, false } }, + { + SQLSRV_ERROR_KEYSTORE_NAME_MISSING, + { IMSSP, (SQLCHAR*) "The name of the custom keystore provider is missing.", -101, false} + }, + { + SQLSRV_ERROR_KEYSTORE_PATH_MISSING, + { IMSSP, (SQLCHAR*) "The path to the custom keystore provider is missing.", -102, false} + }, + { + SQLSRV_ERROR_KEYSTORE_KEY_MISSING, + { IMSSP, (SQLCHAR*) "The encryption key for the custom keystore provider is missing.", -103, false} + }, + { + SQLSRV_ERROR_KEYSTORE_INVALID_VALUE, + { IMSSP, (SQLCHAR*) "Invalid value for loading a custom keystore provider.", -104, false} + }, + // terminate the list of errors/warnings { UINT_MAX, {} } }; From 038241f950ddcd35cf484298c6ab6b4ef0b69259 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 29 Aug 2017 13:27:54 -0700 Subject: [PATCH 33/53] merged msodbcsql.h --- source/shared/msodbcsql.h | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index 9c6e0d41..ce120089 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -370,6 +370,45 @@ #pragma warning(disable:4200) #endif +// Keystore Provider interface definition +typedef struct CEKeystoreContext +{ + void *envCtx; + void *dbcCtx; + void *stmtCtx; +} CEKEYSTORECONTEXT; + +typedef void errFunc(CEKEYSTORECONTEXT *ctx, const wchar_t *msg, ...); + +#define IDS_MSG(x) ((const wchar_t*)(x)) + +typedef struct CEKeystoreProvider +{ + wchar_t *Name; + int (__stdcall *Init)(CEKEYSTORECONTEXT *ctx, errFunc *onError); + int (__stdcall *Read)(CEKEYSTORECONTEXT *ctx, errFunc *onError, void *data, unsigned int *len); + int (__stdcall *Write)(CEKEYSTORECONTEXT *ctx, errFunc *onError, void *data, unsigned int len); + int (__stdcall *DecryptCEK)( + CEKEYSTORECONTEXT *ctx, + errFunc *onError, + const wchar_t *keyPath, + const wchar_t *alg, + unsigned char *ecek, + unsigned short ecekLen, + unsigned char **cekOut, + unsigned short *cekLen); + int(__stdcall *EncryptCEK)( + CEKEYSTORECONTEXT *ctx, + errFunc *onError, + const wchar_t *keyPath, + const wchar_t *alg, + unsigned char *cek, + unsigned short cekLen, + unsigned char **ecekOut, + unsigned short *ecekLen); + void (__stdcall *Free)(); +} CEKEYSTOREPROVIDER; + // Communication between the driver and application via the CEKeystoreData structure typedef struct CEKeystoreData { From 9f68f0193254c106b86a5eb93b9ece9275354cb1 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 23 Aug 2017 13:04:37 -0700 Subject: [PATCH 34/53] added tests for basic error handling --- source/shared/core_conn.cpp | 5 +- test/functional/pdo_sqlsrv/AE_Ksp.inc | 25 +++ .../pdo_connect_encrypted_ksp_errors.phpt | 100 ++++++++++++ test/functional/sqlsrv/AE_Ksp.inc | 25 +++ .../sqlsrv_connect_encrypted_ksp_errors.phpt | 150 ++++++++++++++++++ 5 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/AE_Ksp.inc create mode 100644 test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt create mode 100644 test/functional/sqlsrv/AE_Ksp.inc create mode 100644 test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index eae1d427..93a7e8c9 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -835,8 +835,9 @@ void load_configure_ksp( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) char* encrypt_key = Z_STRVAL_P( conn->ce_option.ksp_encrypt_key ); memcpy_s( pKsd->data, key_size * sizeof( char ) , encrypt_key, key_size ); - core::SQLSetConnectAttr( conn, SQL_COPT_SS_CEKEYSTOREPROVIDER, ksp_path, SQL_NTS ); - core::SQLSetConnectAttr( conn, SQL_COPT_SS_CEKEYSTOREDATA, reinterpret_cast( pKsd ), SQL_IS_POINTER ); + // Will uncomment these two lines when it's ready to test with a real custom keystore provider + // core::SQLSetConnectAttr( conn, SQL_COPT_SS_CEKEYSTOREPROVIDER, ksp_path, SQL_NTS ); + // core::SQLSetConnectAttr( conn, SQL_COPT_SS_CEKEYSTOREDATA, reinterpret_cast( pKsd ), SQL_IS_POINTER ); } void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ) diff --git a/test/functional/pdo_sqlsrv/AE_Ksp.inc b/test/functional/pdo_sqlsrv/AE_Ksp.inc new file mode 100644 index 00000000..fb24a483 --- /dev/null +++ b/test/functional/pdo_sqlsrv/AE_Ksp.inc @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt new file mode 100644 index 00000000..b6a12ad9 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt @@ -0,0 +1,100 @@ +--TEST-- +Fetch data from a prepopulated test table given a custom keystore provider +--SKIPIF-- + +--FILE-- +getMessage() ); + echo "\n"; + } + } + + $ksp_path = getKSPpath(); + + echo("Connecting... with column encryption\n"); + $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; + connect( $connectionInfo ); + + echo("\nConnecting... with an invalid input to CEKeystoreProvider\n"); + $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; + $connectionInfo .= "CEKeystoreName = 1; "; + $connectionInfo .= "CEKeystoreProvider = $ksp_path; "; + $connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; "; + connect( $connectionInfo ); + + echo("\nConnecting... with an empty path\n"); + $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; + $connectionInfo .= "CEKeystoreName = $ksp_name; "; + $connectionInfo .= "CEKeystoreProvider = ; "; + $connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; "; + connect( $connectionInfo ); + + echo("\nConnecting... without a path\n"); + $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; + $connectionInfo .= "CEKeystoreName = $ksp_name; "; + $connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key;"; + connect( $connectionInfo ); + + echo("\nConnecting... without a name\n"); + $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; + $connectionInfo .= "CEKeystoreProvider = $ksp_path; "; + $connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; "; + connect( $connectionInfo ); + + echo("\nConnecting... without a key\n"); + $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; + $connectionInfo .= "CEKeystoreProvider = $ksp_path; "; + $connectionInfo .= "CEKeystoreName = $ksp_name; "; + connect( $connectionInfo ); + + echo("\nConnecting... with all required inputs\n"); + $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; + $connectionInfo .= "CEKeystoreProvider = $ksp_path; "; + $connectionInfo .= "CEKeystoreName = $ksp_name; "; + $connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; "; + connect( $connectionInfo ); + + echo "Done\n"; +?> +--EXPECT-- +Connecting... with column encryption +Connected successfully with ColumnEncryption enabled and KSP specified. + +Connecting... with an invalid input to CEKeystoreProvider +Failed to connect. +SQLSTATE[HY024]: [Microsoft][ODBC Driver 13 for SQL Server]Invalid attribute value + +Connecting... with an empty path +Failed to connect. +SQLSTATE[IMSSP]: Invalid value for loading a custom keystore provider. + +Connecting... without a path +Failed to connect. +SQLSTATE[IMSSP]: The path to the custom keystore provider is missing. + +Connecting... without a name +Failed to connect. +SQLSTATE[IMSSP]: The name of the custom keystore provider is missing. + +Connecting... without a key +Failed to connect. +SQLSTATE[IMSSP]: The encryption key for the custom keystore provider is missing. + +Connecting... with all required inputs +Connected successfully with ColumnEncryption enabled and KSP specified. +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/AE_Ksp.inc b/test/functional/sqlsrv/AE_Ksp.inc new file mode 100644 index 00000000..fb24a483 --- /dev/null +++ b/test/functional/sqlsrv/AE_Ksp.inc @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt new file mode 100644 index 00000000..a2033c11 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt @@ -0,0 +1,150 @@ +--TEST-- +Connect using a custom keystore provider with some required inputs missing +--SKIPIF-- + +--FILE-- +$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled"); + + connect( $server, $connectionInfo ); + + echo("Connecting... with an invalid input to CEKeystoreProvider\n"); + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled", + "CEKeystoreProvider"=>1); + + connect( $server, $connectionInfo ); + + echo("Connecting... with an empty path\n"); + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled", + "CEKeystoreProvider"=>"", + "CEKeystoreName"=>$ksp_name, + "CEKeystoreEncryptKey"=>$encrypt_key); + + connect( $server, $connectionInfo ); + + echo("Connecting... without a name\n"); + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled", + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreEncryptKey"=>$encrypt_key); + + connect( $server, $connectionInfo ); + + echo("Connecting... with an empty name\n"); + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled", + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreName"=>"", + "CEKeystoreEncryptKey"=>$encrypt_key); + + connect( $server, $connectionInfo ); + + echo("Connecting... without a key\n"); + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled", + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreName"=>$ksp_name); + + connect( $server, $connectionInfo ); + + echo("Connecting... with all required inputs\n"); + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled", + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreName"=>$ksp_name, + "CEKeystoreEncryptKey"=>$encrypt_key); + + connect( $server, $connectionInfo ); + + echo "Done\n"; +?> +--EXPECT-- +Connecting... with column encryption +Connected successfully with ColumnEncryption enabled. +Connecting... with an invalid input to CEKeystoreProvider +Failed to connect. +Array +( + [0] => IMSSP + [SQLSTATE] => IMSSP + [1] => -33 + [code] => -33 + [2] => Invalid value type for option CEKeystoreProvider was specified. String type was expected. + [message] => Invalid value type for option CEKeystoreProvider was specified. String type was expected. +) +Connecting... with an empty path +Failed to connect. +Array +( + [0] => IMSSP + [SQLSTATE] => IMSSP + [1] => -104 + [code] => -104 + [2] => Invalid value for loading a custom keystore provider. + [message] => Invalid value for loading a custom keystore provider. +) +Connecting... without a name +Failed to connect. +Array +( + [0] => IMSSP + [SQLSTATE] => IMSSP + [1] => -101 + [code] => -101 + [2] => The name of the custom keystore provider is missing. + [message] => The name of the custom keystore provider is missing. +) +Connecting... with an empty name +Failed to connect. +Array +( + [0] => IMSSP + [SQLSTATE] => IMSSP + [1] => -104 + [code] => -104 + [2] => Invalid value for loading a custom keystore provider. + [message] => Invalid value for loading a custom keystore provider. +) +Connecting... without a key +Failed to connect. +Array +( + [0] => IMSSP + [SQLSTATE] => IMSSP + [1] => -103 + [code] => -103 + [2] => The encryption key for the custom keystore provider is missing. + [message] => The encryption key for the custom keystore provider is missing. +) +Connecting... with all required inputs +Connected successfully with ColumnEncryption enabled. +Done \ No newline at end of file From 237de1e2e2709ad5d106812931ff027b4fa881b0 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 23 Aug 2017 13:16:48 -0700 Subject: [PATCH 35/53] modified a failing pdo test --- .../pdo_connect_encrypted_ksp_errors.phpt | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt index b6a12ad9..9756a361 100644 --- a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt @@ -30,12 +30,13 @@ Fetch data from a prepopulated test table given a custom keystore provider $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; connect( $connectionInfo ); - echo("\nConnecting... with an invalid input to CEKeystoreProvider\n"); - $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; - $connectionInfo .= "CEKeystoreName = 1; "; - $connectionInfo .= "CEKeystoreProvider = $ksp_path; "; - $connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; "; - connect( $connectionInfo ); + // will comment these when it's ready to test with a real KSP + // echo("\nConnecting... with an invalid input to CEKeystoreProvider\n"); + // $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; + // $connectionInfo .= "CEKeystoreName = 1; "; + // $connectionInfo .= "CEKeystoreProvider = $ksp_path; "; + // $connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; "; + // connect( $connectionInfo ); echo("\nConnecting... with an empty path\n"); $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; @@ -75,10 +76,6 @@ Fetch data from a prepopulated test table given a custom keystore provider Connecting... with column encryption Connected successfully with ColumnEncryption enabled and KSP specified. -Connecting... with an invalid input to CEKeystoreProvider -Failed to connect. -SQLSTATE[HY024]: [Microsoft][ODBC Driver 13 for SQL Server]Invalid attribute value - Connecting... with an empty path Failed to connect. SQLSTATE[IMSSP]: Invalid value for loading a custom keystore provider. From 3a50fc7f61467ce9d9bd1ac11693a18aa32cfdb4 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 23 Aug 2017 16:47:31 -0700 Subject: [PATCH 36/53] modified the KSP include files --- test/functional/pdo_sqlsrv/AE_Ksp.inc | 2 +- test/functional/sqlsrv/AE_Ksp.inc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/pdo_sqlsrv/AE_Ksp.inc b/test/functional/pdo_sqlsrv/AE_Ksp.inc index fb24a483..8b845f7c 100644 --- a/test/functional/pdo_sqlsrv/AE_Ksp.inc +++ b/test/functional/pdo_sqlsrv/AE_Ksp.inc @@ -9,7 +9,7 @@ function getKSPpath() if ( strtoupper( substr( php_uname( 's' ), 0, 3 ) ) == 'WIN' ) { $arch = 'x64'; if ( PHP_INT_SIZE == 4 ) // running 32 bit - $arch = 'x86'; + $arch = ''; $ksp .= $arch . '.dll'; } else diff --git a/test/functional/sqlsrv/AE_Ksp.inc b/test/functional/sqlsrv/AE_Ksp.inc index fb24a483..8b845f7c 100644 --- a/test/functional/sqlsrv/AE_Ksp.inc +++ b/test/functional/sqlsrv/AE_Ksp.inc @@ -9,7 +9,7 @@ function getKSPpath() if ( strtoupper( substr( php_uname( 's' ), 0, 3 ) ) == 'WIN' ) { $arch = 'x64'; if ( PHP_INT_SIZE == 4 ) // running 32 bit - $arch = 'x86'; + $arch = ''; $ksp .= $arch . '.dll'; } else From 1307f3225bf6f19b47c10e7bd0f7b118a19b90a0 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 29 Aug 2017 13:35:50 -0700 Subject: [PATCH 37/53] merged changes to travis and Docker --- .travis.yml | 11 +- Dockerfile-msphpsql | 3 + appveyor.yml | 6 + test/functional/setup/build_ksp.py | 117 ++++++++++++ test/functional/setup/ksp_app.c | 280 +++++++++++++++++++++++++++++ test/functional/setup/myKSP.c | 132 ++++++++++++++ test/functional/setup/run_ksp.py | 52 ++++++ 7 files changed, 600 insertions(+), 1 deletion(-) create mode 100644 test/functional/setup/build_ksp.py create mode 100644 test/functional/setup/ksp_app.c create mode 100644 test/functional/setup/myKSP.c create mode 100644 test/functional/setup/run_ksp.py diff --git a/.travis.yml b/.travis.yml index c290dccf..17acc8bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,11 @@ env: - PHPSQLDIR=/REPO/msphpsql-dev - TEST_PHP_SQL_SERVER=sql - SQLSRV_DBNAME=msphpsql_sqlsrv - - PDOSQLSRV_DBNAME=msphpsql_pdosqlsrv + - PDOSQLSRV_DBNAME=msphpsql_pdosqlsrv + - TEST_PHP_SQL_UID=sa + - TEST_PHP_SQL_PWD=Password12@ + +>>>>>>> 22838b1... added KSP related code and scripts to enable testing KSP before_install: - docker pull microsoft/mssql-server-linux @@ -33,6 +37,11 @@ script: - docker logs client - travis_retry docker exec client python ./test/functional/setup/setup_dbs.py -dbname $SQLSRV_DBNAME - travis_retry docker exec client python ./test/functional/setup/setup_dbs.py -dbname $PDOSQLSRV_DBNAME + - travis_retry docker exec client python ./test/functional/setup/build_ksp.py + - travis_retry docker exec client cp ./test/functional/setup/myKSP.so ./test/functional/sqlsrv/ + - travis_retry docker exec client cp ./test/functional/setup/myKSP.so ./test/functional/pdo_sqlsrv/ + - travis_retry docker exec client python ./test/functional/setup/run_ksp.py -server $TEST_PHP_SQL_SERVER -dbname $SQLSRV_DBNAME -uid $TEST_PHP_SQL_UID -pwd $TEST_PHP_SQL_PWD + - travis_retry docker exec client python ./test/functional/setup/run_ksp.py -server $TEST_PHP_SQL_SERVER -dbname $PDOSQLSRV_DBNAME -uid $TEST_PHP_SQL_UID -pwd $TEST_PHP_SQL_PWD - travis_retry docker exec client php ./source/pdo_sqlsrv/run-tests.php ./test/functional/pdo_sqlsrv/*.phpt - travis_retry docker exec client php ./source/sqlsrv/run-tests.php ./test/functional/sqlsrv/*.phpt - docker exec client bash -c 'for f in ./test/functional/sqlsrv/*.diff; do ls $f 2>/dev/null; cat $f 2>/dev/null; done || true' diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql index 6cff1116..9a554d95 100644 --- a/Dockerfile-msphpsql +++ b/Dockerfile-msphpsql @@ -78,5 +78,8 @@ WORKDIR $PHPSQLDIR RUN chmod +x ./entrypoint.sh CMD /bin/bash ./entrypoint.sh +# copy odbc header file to setup +COPY $PHPSQLDIR/source/shared/msodbcsql.h $PHPSQLDIR/test/functional/setup + ENV REPORT_EXIT_STATUS 1 ENV TEST_PHP_EXECUTABLE /usr/bin/php \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 58d7d91c..66ba81da 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -166,6 +166,12 @@ test_script: - python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\setup_dbs.py -dbname %SQLSRV_DBNAME% - Echo setup test database for PDO_SQLSRV tests - %PDOSQLSRV_DBNAME% - python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\setup_dbs.py -dbname %PDOSQLSRV_DBNAME% + - copy %APPVEYOR_BUILD_FOLDER%\source\shared\msodbcsql.h %APPVEYOR_BUILD_FOLDER%\test\functional\setup\ + - python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\build_ksp.py + - copy %APPVEYOR_BUILD_FOLDER%\test\functional\setup\*.dll %APPVEYOR_BUILD_FOLDER%\test\functional\sqlsrv\ + - copy %APPVEYOR_BUILD_FOLDER%\test\functional\setup\*.dll %APPVEYOR_BUILD_FOLDER%\test\functional\pdo_sqlsrv\ + - python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\run_ksp.py -server %TEST_PHP_SQL_SERVER% -dbname %SQLSRV_DBNAME% -uid %TEST_PHP_SQL_UID% -pwd %TEST_PHP_SQL_PWD% + - python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\run_ksp.py -server %TEST_PHP_SQL_SERVER% -dbname %PDOSQLSRV_DBNAME% -uid %TEST_PHP_SQL_UID% -pwd %TEST_PHP_SQL_PWD% - php run-tests.php -p php.exe %APPVEYOR_BUILD_FOLDER%\test\functional\sqlsrv\*.phpt > %APPVEYOR_BUILD_FOLDER%\test\functional\sqlsrv.log 2>&1 - type %APPVEYOR_BUILD_FOLDER%\test\functional\sqlsrv.log - php run-tests.php -p php.exe %APPVEYOR_BUILD_FOLDER%\test\functional\pdo_sqlsrv\*.phpt > %APPVEYOR_BUILD_FOLDER%\test\functional\pdo_sqlsrv.log 2>&1 diff --git a/test/functional/setup/build_ksp.py b/test/functional/setup/build_ksp.py new file mode 100644 index 00000000..54b661fe --- /dev/null +++ b/test/functional/setup/build_ksp.py @@ -0,0 +1,117 @@ +#!/usr/bin/python3 +######################################################################################### +# +# Description: This script builds a custom keystore provider and compiles the app that +# uses this KSP. Their names can be passed as arguments, but the outputs +# are always +# - myKSP.dll (myKSPx64.dll) / myKSP.so +# - ksp_app.exe / ksp_app +# +# Requirement: +# python 3.x +# myKSP.c (or any equivalent) +# ksp_app.c (or any equivalent) +# msodbcsql.h (odbc header file) +# +# Execution: Run with command line with required options +# py build_ksp.py --KSP myKSP --APP ksp_app +# +############################################################################################# + +import sys +import os +import platform +import argparse + +# This returns the path to the batch file vcvarsall.bat that comes with Visual Studio +def getCompilerPath(): + root_dir = 'C:' + os.sep + return os.path.join(root_dir, "Program Files (x86)", "Microsoft Visual Studio 14.0", "VC", "vcvarsall.bat") + +# This creates a batch file to compile myKSP.c into a dll, according to *arch* (either x86 or x64) +def create_batch_file(arch, filename, command): + vcvarsall = getCompilerPath() + try: + file = open(filename, 'w') + file.write('@ECHO OFF' + os.linesep) + if arch == 'x64': + file.write('@CALL "' + vcvarsall + '" amd64' + os.linesep) + else: + file.write('@CALL "' + vcvarsall + '" x86' + os.linesep) + + # compile the code + file.write('@CALL ' + command + os.linesep) + file.close() + except: + print('Cannot create ', filename) + +# This invokes the newly created batch file to compile the code, +# according to *arch* (either x86 or x64). The batch file will be +# removed afterwards +def compile_KSP_windows(arch, ksp_name): + output = 'myKSP' + if arch == 'x64': + output = output + arch + '.dll' + else: + output = output + '.dll' + + command = 'cl {0}.c /LD /MD /link /out:'.format(ksp_name) + output + batchfile = 'build_KSP.bat' + create_batch_file(arch, batchfile, command) + os.system(batchfile) + os.remove(batchfile) + +# This compiles myKSP.c +# +# In Windows, this will create batch files to compile two dll(s). +# Otherwise, this will compile the code and generate a .so file. +# +# Output: A custom keystore provider created +def compile_KSP(ksp_name): + print('Compiling ', ksp_name) + if platform.system() == 'Windows': + compile_KSP_windows('x64', ksp_name) + compile_KSP_windows('x86', ksp_name) + else: + os.system('gcc -fshort-wchar -fPIC -o myKSP.so -shared {0}.c'.format(ksp_name)) + +# This compiles ksp app, which assumes the existence of the .dll or the .so file. +# +# In Windows, a batch file is created in order to compile the code. +def configure_KSP(app_name): + print('Compiling ', app_name) + if platform.system() == 'Windows': + command = 'cl /MD {0}.c /link odbc32.lib /out:ksp_app.exe'.format(app_name) + batchfile = 'build_app.bat' + create_batch_file('x86', batchfile, command) + os.system(batchfile) + os.remove(batchfile) + else: + os.system('gcc -o ksp_app -fshort-wchar {0}.c -lodbc -ldl'.format(app_name)) + +################################### Main Function ################################### +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-ksp', '--KSPNAME', default='myKSP', help='The KSP (keystore provider)') + parser.add_argument('-app', '--APPNAME', default='ksp_app', help='The app that uses the KSP') + args = parser.parse_args() + + ksp_name = args.KSPNAME + app_name = args.APPNAME + header = 'msodbcsql.h' + + # make sure all required source and header files are present + work_dir = os.path.dirname(os.path.realpath(__file__)) + if not os.path.exists(os.path.join(work_dir, header)): + print('Error: {0} not found!'.format(header)) + exit(1) + if not os.path.exists(os.path.join(work_dir, ksp_name + '.c')): + print('Error: {0}.c not found!'.format(ksp_name)) + exit(1) + if not os.path.exists(os.path.join(work_dir, app_name + '.c')): + print('Error: {0}.c not found!'.format(app_name)) + exit(1) + + compile_KSP(ksp_name) + configure_KSP(app_name) + \ No newline at end of file diff --git a/test/functional/setup/ksp_app.c b/test/functional/setup/ksp_app.c new file mode 100644 index 00000000..74d22e37 --- /dev/null +++ b/test/functional/setup/ksp_app.c @@ -0,0 +1,280 @@ +/****************************************************************************** + Example application for demonstration of custom keystore provider usage + + Windows: compile with cl /MD ksp_app.c /link odbc32.lib + Linux/mac: compile with gcc -o ksp_app -fshort-wchar ksp_app.c -lodbc -ldl + + usage: kspapp connstr + + ******************************************************************************/ + +#define KSPNAME L"MyCustomKSPName" +#define PROV_ENCRYPT_KEY "LPKCWVD07N3RG98J0MBLG4H2" /* this can be any character string */ + +#include +#include +#ifdef _WIN32 +#include +#else +#define __stdcall +#include +#endif +#include +#include +#include "msodbcsql.h" + +enum job { + set_up = 0, + clean_up = 1 +}; + +/* Convenience functions */ + +int checkRC(SQLRETURN rc, char *msg, int ret, SQLHANDLE h, SQLSMALLINT ht) { + if (rc == SQL_ERROR) { + fprintf(stderr, "Error occurred upon %s\n", msg); + if (h) { + SQLSMALLINT i = 0; + SQLSMALLINT outlen = 0; + char errmsg[1024]; + while ((rc = SQLGetDiagField( + ht, h, ++i, SQL_DIAG_MESSAGE_TEXT, errmsg, sizeof(errmsg), &outlen)) == SQL_SUCCESS + || rc == SQL_SUCCESS_WITH_INFO) { + fprintf(stderr, "Err#%d: %s\n", i, errmsg); + } + } + if (ret) + exit(ret); + return 0; + } + else if (rc == SQL_SUCCESS_WITH_INFO && h) { + SQLSMALLINT i = 0; + SQLSMALLINT outlen = 0; + char errmsg[1024]; + printf("Success with info for %s:\n", msg); + while ((rc = SQLGetDiagField( + ht, h, ++i, SQL_DIAG_MESSAGE_TEXT, errmsg, sizeof(errmsg), &outlen)) == SQL_SUCCESS + || rc == SQL_SUCCESS_WITH_INFO) { + fprintf(stderr, "Msg#%d: %s\n", i, errmsg); + } + } + return 1; +} + +void postKspError(CEKEYSTORECONTEXT *ctx, const wchar_t *msg, ...) { + if (msg > (wchar_t*)65535) + wprintf(L"Provider emitted message: %s\n", msg); + else + wprintf(L"Provider emitted message ID %d\n", msg); +} + +int setKSPLibrary(SQLHSTMT stmt) { + unsigned char CEK[32]; + unsigned char *ECEK; + unsigned short ECEKlen; + int i; +#ifdef _WIN32 + HMODULE hProvLib; +#else + void *hProvLib; +#endif + CEKEYSTORECONTEXT ctx = {0}; + CEKEYSTOREPROVIDER **ppKsp, *pKsp; + int(__stdcall *pEncryptCEK)(CEKEYSTORECONTEXT *, errFunc *, unsigned char *, unsigned short, unsigned char **, unsigned short *); + + /* Load the provider library */ +#ifdef _WIN32 + if (!(hProvLib = LoadLibrary("myKSP.dll"))) { +#else + if (!(hProvLib = dlopen("./myKSP.so", RTLD_NOW))) { +#endif + fprintf(stderr, "Error loading KSP library\n"); + return 2; + } +#ifdef _WIN32 + if (!(ppKsp = (CEKEYSTOREPROVIDER**)GetProcAddress(hProvLib, "CEKeystoreProvider"))) { +#else + if (!(ppKsp = (CEKEYSTOREPROVIDER**)dlsym(hProvLib, "CEKeystoreProvider"))) { +#endif + fprintf(stderr, "The export CEKeystoreProvider was not found in the KSP library\n"); + return 3; + } + while (pKsp = *ppKsp++) { + if (!memcmp(KSPNAME, pKsp->Name, sizeof(KSPNAME))) + goto FoundProv; + } + fprintf(stderr, "Could not find provider in the library\n"); + return 4; +FoundProv: + if (pKsp->Init && !pKsp->Init(&ctx, postKspError)) { + fprintf(stderr, "Could not initialize provider\n"); + return 5; + } +#ifdef _WIN32 + if (!(pEncryptCEK = (LPVOID)GetProcAddress(hProvLib, "KeystoreEncrypt"))) { +#else + if (!(pEncryptCEK = dlsym(hProvLib, "KeystoreEncrypt"))) { +#endif + fprintf(stderr, "The export KeystoreEncrypt was not found in the KSP library\n"); + return 6; + } + if (!pKsp->Write) { + fprintf(stderr, "Provider does not support configuration\n"); + return 7; + } + + /* Configure the provider with the key */ + if (!pKsp->Write(&ctx, postKspError, PROV_ENCRYPT_KEY, strlen(PROV_ENCRYPT_KEY))) { + fprintf(stderr, "Error writing to KSP\n"); + return 8; + } + + /* Generate a CEK and encrypt it with the provider */ + srand(time(0) ^ getpid()); + for (i = 0; i < sizeof(CEK); i++) + CEK[i] = rand(); + + if (!pEncryptCEK(&ctx, postKspError, CEK, sizeof(CEK), &ECEK, &ECEKlen)) { + fprintf(stderr, "Error encrypting CEK\n"); + return 9; + } + + /* Create a CMK definition on the server */ + { + static char cmkSql[] = "CREATE COLUMN MASTER KEY CustomCMK WITH (" + "KEY_STORE_PROVIDER_NAME = 'MyCustomKSPName'," + "KEY_PATH = 'TheOneAndOnlyKey')"; + printf("Create CMK: %s\n", cmkSql); + SQLExecDirect(stmt, cmkSql, SQL_NTS); + } + + /* Create a CEK definition on the server */ + { + const char cekSqlBefore[] = "CREATE COLUMN ENCRYPTION KEY CustomCEK WITH VALUES (" + "COLUMN_MASTER_KEY = CustomCMK," + "ALGORITHM = 'none'," + "ENCRYPTED_VALUE = 0x"; + char *cekSql = malloc(sizeof(cekSqlBefore) + 2 * ECEKlen + 2); /* 1 for ')', 1 for null terminator */ + strcpy(cekSql, cekSqlBefore); + for (i = 0; i < ECEKlen; i++) + sprintf(cekSql + sizeof(cekSqlBefore) - 1 + 2 * i, "%02x", ECEK[i]); + strcat(cekSql, ")"); + printf("Create CEK: %s\n", cekSql); + SQLExecDirect(stmt, cekSql, SQL_NTS); + free(cekSql); +#ifdef _WIN32 + LocalFree(ECEK); +#else + free(ECEK); +#endif + } + +#ifdef _WIN32 + FreeLibrary(hProvLib); +#else + dlclose(hProvLib); +#endif + + return 0; +} + +void populateTestTable(SQLHDBC dbc, SQLHSTMT stmt) +{ + SQLRETURN rc; + int i; + + /* Create a table with encrypted columns */ + { + static char *tableSql = "CREATE TABLE CustomKSPTestTable (" + "c1 int," + "c2 varchar(255) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256'))"; + printf("Create table: %s\n", tableSql); + SQLExecDirect(stmt, tableSql, SQL_NTS); + } + + /* Load provider into the ODBC Driver and configure it */ + { + unsigned char ksd[sizeof(CEKEYSTOREDATA) + sizeof(PROV_ENCRYPT_KEY) - 1]; + CEKEYSTOREDATA *pKsd = (CEKEYSTOREDATA*)ksd; + pKsd->name = L"MyCustomKSPName"; + pKsd->dataSize = sizeof(PROV_ENCRYPT_KEY) - 1; + memcpy(pKsd->data, PROV_ENCRYPT_KEY, sizeof(PROV_ENCRYPT_KEY) - 1); +#ifdef _WIN32 + rc = SQLSetConnectAttr(dbc, SQL_COPT_SS_CEKEYSTOREPROVIDER, "myKSP.dll", SQL_NTS); +#else + rc = SQLSetConnectAttr(dbc, SQL_COPT_SS_CEKEYSTOREPROVIDER, "./myKSP.so", SQL_NTS); +#endif + checkRC(rc, "Loading KSP into ODBC Driver", 7, dbc, SQL_HANDLE_DBC); + rc = SQLSetConnectAttr(dbc, SQL_COPT_SS_CEKEYSTOREDATA, (SQLPOINTER)pKsd, SQL_IS_POINTER); + checkRC(rc, "Configuring the KSP", 7, dbc, SQL_HANDLE_DBC); + } + + /* Insert some data */ + { + int c1; + char c2[256]; + rc = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &c1, 0, 0); + checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT); + rc = SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 255, 0, c2, 255, 0); + checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT); + for (i = 0; i < 10; i++) { + c1 = i * 10 + i + 1; + sprintf(c2, "Sample data %d for column 2", i); + rc = SQLExecDirect(stmt, "INSERT INTO CustomKSPTestTable (c1, c2) values (?, ?)", SQL_NTS); + checkRC(rc, "Inserting rows query", 10, stmt, SQL_HANDLE_STMT); + } + printf("(Encrypted) data has been inserted into CustomKSPTestTable. You may inspect the data now.\n"); + } + +} + +int main(int argc, char **argv) { + char sqlbuf[1024]; + SQLHENV env; + SQLHDBC dbc; + SQLHSTMT stmt; + SQLRETURN rc; + int i; + char connStr[1024]; + enum job task; + + if (argc < 6) { + fprintf(stderr, "usage: kspapp job server database uid pwd\n"); + return 1; + } + + task = atoi(argv[1]); + + sprintf(connStr, "DRIVER={ODBC Driver 13 for SQL Server};SERVER=%s;ColumnEncryption=Enabled;DATABASE=%s;UID=%s;PWD=%s", argv[2], argv[3], argv[4], argv[5]); + + /* Connect to Server */ + rc = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &env); + checkRC(rc, "allocating environment handle", 2, 0, 0); + rc = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0); + checkRC(rc, "setting ODBC version to 3.0", 3, env, SQL_HANDLE_ENV); + rc = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc); + checkRC(rc, "allocating connection handle", 4, env, SQL_HANDLE_ENV); + rc = SQLDriverConnect(dbc, 0, connStr, strlen(connStr), NULL, 0, NULL, SQL_DRIVER_NOPROMPT); + checkRC(rc, "connecting to data source", 5, dbc, SQL_HANDLE_DBC); + rc = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt); + checkRC(rc, "allocating statement handle", 6, dbc, SQL_HANDLE_DBC); + + if (task == set_up) { + printf("Setting up KSP...\n"); + setKSPLibrary(stmt); + populateTestTable(dbc, stmt); + } + else if (task == clean_up) { + printf("Cleaning up KSP...\n"); + + SQLExecDirect(stmt, "DROP TABLE CustomKSPTestTable", SQL_NTS); + SQLExecDirect(stmt, "DROP COLUMN ENCRYPTION KEY CustomCEK", SQL_NTS); + SQLExecDirect(stmt, "DROP COLUMN MASTER KEY CustomCMK", SQL_NTS); + printf("Removed table, CEK, and CMK\n"); + } + + SQLDisconnect(dbc); + SQLFreeHandle(SQL_HANDLE_DBC, dbc); + SQLFreeHandle(SQL_HANDLE_ENV, env); + return 0; +} \ No newline at end of file diff --git a/test/functional/setup/myKSP.c b/test/functional/setup/myKSP.c new file mode 100644 index 00000000..d6188842 --- /dev/null +++ b/test/functional/setup/myKSP.c @@ -0,0 +1,132 @@ +/****************************************************************************** +Custom Keystore Provider Example + +Windows: compile with cl myKSP.c /LD /MD /link /out:myKSP.dll +Linux/mac: compile with gcc -fshort-wchar -fPIC -o myKSP.so -shared myKSP.c + +******************************************************************************/ + +#ifdef _WIN32 +#include +#else +#define __stdcall +#endif + +#define DEBUG 0 + +#include +#include +#include +#include +#include +#include "msodbcsql.h" + +int wcscmp_short(wchar_t *s1, wchar_t *s2) { + while(*s1 && *s2 && *s1 == *s2) + s1++, s2++; + return *s1 - *s2; +} + +int __stdcall KeystoreInit(CEKEYSTORECONTEXT *ctx, errFunc *onError) { + if (DEBUG) + printf("KSP Init() function called\n"); + return 1; +} + +static unsigned char *g_encryptKey; +static unsigned int g_encryptKeyLen; + +int __stdcall KeystoreWrite(CEKEYSTORECONTEXT *ctx, errFunc *onError, void *data, unsigned int len) { + if (DEBUG) + printf("KSP Write() function called (%d bytes)\n", len); + if (len) { + if (g_encryptKey) + free(g_encryptKey); + g_encryptKey = malloc(len); + if (!g_encryptKey) { + onError(ctx, L"Memory Allocation Error"); + return 0; + } + memcpy(g_encryptKey, data, len); + g_encryptKeyLen = len; + } + return 1; +} + +// Very simple "encryption" scheme - rotating XOR with the key +int __stdcall KeystoreDecrypt(CEKEYSTORECONTEXT *ctx, errFunc *onError, const wchar_t *keyPath, const wchar_t *alg, unsigned char *ecek, unsigned short ecekLen, unsigned char **cekOut, unsigned short *cekLen) { + unsigned int i; + if (DEBUG) + printf("KSP Decrypt() function called (keypath=%S alg=%S ecekLen=%u)\n", keyPath, alg, ecekLen); + if (wcscmp_short(keyPath, L"TheOneAndOnlyKey")) { + onError(ctx, L"Invalid key path"); + return 0; + } + if (wcscmp_short(alg, L"none")) { + onError(ctx, L"Invalid algorithm"); + return 0; + } + if (!g_encryptKey) { + onError(ctx, L"Keystore provider not initialized with key"); + return 0; + } +#ifndef _WIN32 + *cekOut = malloc(ecekLen); +#else + *cekOut = LocalAlloc(LMEM_FIXED, ecekLen); +#endif + if (!*cekOut) { + onError(ctx, L"Memory Allocation Error"); + return 0; + } + *cekLen = ecekLen; + for (i = 0; i < ecekLen; i++) + (*cekOut)[i] = ecek[i] ^ g_encryptKey[i % g_encryptKeyLen]; + return 1; +} + +// Note that in the provider interface, this function would be referenced via the CEKEYSTOREPROVIDER +// structure. However, that does not preclude keystore providers from exporting their own functions, +// as illustrated by this example where the encryption is performed via a separate function (with a +// different prototype than the one in the KSP interface.) +#ifdef _WIN32 +__declspec(dllexport) +#endif +int KeystoreEncrypt(CEKEYSTORECONTEXT *ctx, errFunc *onError, + unsigned char *cek, unsigned short cekLen, + unsigned char **ecekOut, unsigned short *ecekLen) { + unsigned int i; + + if (DEBUG) + printf("KSP Encrypt() function called (cekLen=%u)\n", cekLen); + if (!g_encryptKey) { + onError(ctx, L"Keystore provider not initialized with key"); + return 0; + } + *ecekOut = malloc(cekLen); + if (!*ecekOut) { + onError(ctx, L"Memory Allocation Error"); + return 0; + } + *ecekLen = cekLen; + for (i = 0; i < cekLen; i++) + (*ecekOut)[i] = cek[i] ^ g_encryptKey[i % g_encryptKeyLen]; + return 1; +} + +CEKEYSTOREPROVIDER MyCustomKSPName_desc = { + L"MyCustomKSPName", + KeystoreInit, + 0, + KeystoreWrite, + KeystoreDecrypt, + 0 +}; + +#ifdef _WIN32 +__declspec(dllexport) +#endif +CEKEYSTOREPROVIDER *CEKeystoreProvider[] = { + &MyCustomKSPName_desc, + 0 +}; \ No newline at end of file diff --git a/test/functional/setup/run_ksp.py b/test/functional/setup/run_ksp.py new file mode 100644 index 00000000..411807c5 --- /dev/null +++ b/test/functional/setup/run_ksp.py @@ -0,0 +1,52 @@ +#!/usr/bin/python3 +######################################################################################### +# +# Description: This script assumes the existence of the ksp_app executable and will +# invoke it to create / remove the Column Master Key, the Column Encryption key, +# and the table [CustomKSPTestTable] in the test database. +# +# Requirement: +# python 3.x +# ksp_app executable +# +# Execution: Run with command line with required options +# py run_ksp.py --SERVER=server --DBNAME=database --UID=uid --PWD=pwd +# py run_ksp.py --SERVER=server --DBNAME=database --UID=uid --PWD=pwd --REMOVE +# +############################################################################################# + +import sys +import os +import platform +import argparse + +################################### Main Function ################################### +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-server', '--SERVER', required=True, help='SQL Server') + parser.add_argument('-dbname', '--DBNAME', required=True, help='Name of an existing database') + parser.add_argument('-uid', '--UID', required=True, help='User name') + parser.add_argument('-pwd', '--PWD', required=True, help='User password') + parser.add_argument('-remove', '--REMOVE', action='store_true', help='Clean up KSP related data, false by default') + + args = parser.parse_args() + + app_name = 'ksp_app' + + # first check if the ksp app is present + work_dir = os.path.dirname(os.path.realpath(__file__)) + if platform.system() == 'Windows': + path = os.path.join(work_dir, app_name + '.exe') + executable = app_name + else: + path = os.path.join(work_dir, app_name) + executable = './' + app_name + + if not os.path.exists(path): + print('Error: {0} not found!'.format(path)) + exit(1) + + if args.REMOVE: + os.system('{0} 1 {1} {2} {3} {4}'.format(executable, args.SERVER, args.DBNAME, args.UID, args.PWD)) + else: + os.system('{0} 0 {1} {2} {3} {4}'.format(executable, args.SERVER, args.DBNAME, args.UID, args.PWD)) From 6f47bf9a1a8f9d26c778a08c63c854b3f35a2038 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 24 Aug 2017 10:24:21 -0700 Subject: [PATCH 38/53] use working directory --- test/functional/setup/build_ksp.py | 7 +++++++ test/functional/setup/run_ksp.py | 7 ++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/test/functional/setup/build_ksp.py b/test/functional/setup/build_ksp.py index 54b661fe..27b07997 100644 --- a/test/functional/setup/build_ksp.py +++ b/test/functional/setup/build_ksp.py @@ -100,8 +100,12 @@ if __name__ == '__main__': app_name = args.APPNAME header = 'msodbcsql.h' + cwd = os.getcwd() + # make sure all required source and header files are present work_dir = os.path.dirname(os.path.realpath(__file__)) + os.chdir(work_dir) + if not os.path.exists(os.path.join(work_dir, header)): print('Error: {0} not found!'.format(header)) exit(1) @@ -114,4 +118,7 @@ if __name__ == '__main__': compile_KSP(ksp_name) configure_KSP(app_name) + + os.chdir(cwd) + \ No newline at end of file diff --git a/test/functional/setup/run_ksp.py b/test/functional/setup/run_ksp.py index 411807c5..0d0ff897 100644 --- a/test/functional/setup/run_ksp.py +++ b/test/functional/setup/run_ksp.py @@ -32,9 +32,12 @@ if __name__ == '__main__': args = parser.parse_args() app_name = 'ksp_app' - + cwd = os.getcwd() + # first check if the ksp app is present work_dir = os.path.dirname(os.path.realpath(__file__)) + os.chdir(work_dir) + if platform.system() == 'Windows': path = os.path.join(work_dir, app_name + '.exe') executable = app_name @@ -50,3 +53,5 @@ if __name__ == '__main__': os.system('{0} 1 {1} {2} {3} {4}'.format(executable, args.SERVER, args.DBNAME, args.UID, args.PWD)) else: os.system('{0} 0 {1} {2} {3} {4}'.format(executable, args.SERVER, args.DBNAME, args.UID, args.PWD)) + + os.chdir(cwd) From a6b54058f967f84cdd8969b4e9b5436dadc8f26f Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 24 Aug 2017 10:55:27 -0700 Subject: [PATCH 39/53] added checks for server version in the tests --- Dockerfile-msphpsql | 11 ++++++++ source/shared/core_conn.cpp | 5 ++-- .../pdo_connect_encrypted_ksp_errors.phpt | 2 +- .../pdo_sqlsrv/skipif_server_old.inc | 19 ++++++++++++++ test/functional/sqlsrv/skipif_server_old.inc | 25 +++++++++++++++++++ .../sqlsrv_connect_encrypted_ksp_errors.phpt | 2 +- 6 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/skipif_server_old.inc create mode 100644 test/functional/sqlsrv/skipif_server_old.inc diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql index 9a554d95..fcdaba6e 100644 --- a/Dockerfile-msphpsql +++ b/Dockerfile-msphpsql @@ -44,9 +44,20 @@ RUN pip install --upgrade pip && pip install cpp-coveralls #Either Install git / download zip (One can see other strategies : https://ryanfb.github.io/etc/2015/07/29/git_strategies_for_docker.html ) #One option is to get source from zip file of repository. +<<<<<<< HEAD #another option is to copy source to build directory on image RUN mkdir -p $PHPSQLDIR COPY . $PHPSQLDIR +======= + +# another option is to copy source to build directory on image +RUN mkdir -p $PHPSQLDIR +COPY . $PHPSQLDIR + +# copy odbc header file to setup +COPY $PHPSQLDIR/source/shared/msodbcsql.h $PHPSQLDIR/test/functional/setup + +>>>>>>> 328286b... added checks for server version in the tests WORKDIR $PHPSQLDIR/source/ RUN chmod +x ./packagize.sh diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 93a7e8c9..eae1d427 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -835,9 +835,8 @@ void load_configure_ksp( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) char* encrypt_key = Z_STRVAL_P( conn->ce_option.ksp_encrypt_key ); memcpy_s( pKsd->data, key_size * sizeof( char ) , encrypt_key, key_size ); - // Will uncomment these two lines when it's ready to test with a real custom keystore provider - // core::SQLSetConnectAttr( conn, SQL_COPT_SS_CEKEYSTOREPROVIDER, ksp_path, SQL_NTS ); - // core::SQLSetConnectAttr( conn, SQL_COPT_SS_CEKEYSTOREDATA, reinterpret_cast( pKsd ), SQL_IS_POINTER ); + core::SQLSetConnectAttr( conn, SQL_COPT_SS_CEKEYSTOREPROVIDER, ksp_path, SQL_NTS ); + core::SQLSetConnectAttr( conn, SQL_COPT_SS_CEKEYSTOREDATA, reinterpret_cast( pKsd ), SQL_IS_POINTER ); } void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ) diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt index 9756a361..3b1da719 100644 --- a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt @@ -1,7 +1,7 @@ --TEST-- Fetch data from a prepopulated test table given a custom keystore provider --SKIPIF-- - + --FILE-- getAttribute(constant('PDO::ATTR_SERVER_VERSION')); +$version = substr($attr, 0, 2); +if ($version < 13) +{ + die( "skip - feature not supported in this version of SQL Server." ); +} +?> \ No newline at end of file diff --git a/test/functional/sqlsrv/skipif_server_old.inc b/test/functional/sqlsrv/skipif_server_old.inc new file mode 100644 index 00000000..3ee03362 --- /dev/null +++ b/test/functional/sqlsrv/skipif_server_old.inc @@ -0,0 +1,25 @@ +$userName, "PWD"=>$userPassword ); + +$conn = sqlsrv_connect( $server, $connectionInfo ); +if( ! $conn ) +{ + die( "skip - could not connect during SKIPIF." ); +} + +$server_info = sqlsrv_server_info( $conn ); +if( $server_info ) +{ + // check SQL Server version + $version = substr($server_info['SQLServerVersion'], 0, 2); + if ($version < 13) + { + die( "skip - feature not supported in this version of SQL Server." ); + } +} +?> \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt index a2033c11..be6204e0 100644 --- a/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt @@ -1,7 +1,7 @@ --TEST-- Connect using a custom keystore provider with some required inputs missing --SKIPIF-- - + --FILE-- Date: Tue, 29 Aug 2017 13:40:29 -0700 Subject: [PATCH 40/53] resolved conflicts in travis and docker --- .travis.yml | 5 +++-- Dockerfile-msphpsql | 14 -------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 17acc8bf..b52af53c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,9 +37,10 @@ script: - docker logs client - travis_retry docker exec client python ./test/functional/setup/setup_dbs.py -dbname $SQLSRV_DBNAME - travis_retry docker exec client python ./test/functional/setup/setup_dbs.py -dbname $PDOSQLSRV_DBNAME + - docker exec client cp ./source/shared/msodbcsql.h ./test/functional/setup/ - travis_retry docker exec client python ./test/functional/setup/build_ksp.py - - travis_retry docker exec client cp ./test/functional/setup/myKSP.so ./test/functional/sqlsrv/ - - travis_retry docker exec client cp ./test/functional/setup/myKSP.so ./test/functional/pdo_sqlsrv/ + - docker exec client cp ./test/functional/setup/myKSP.so ./test/functional/sqlsrv/ + - docker exec client cp ./test/functional/setup/myKSP.so ./test/functional/pdo_sqlsrv/ - travis_retry docker exec client python ./test/functional/setup/run_ksp.py -server $TEST_PHP_SQL_SERVER -dbname $SQLSRV_DBNAME -uid $TEST_PHP_SQL_UID -pwd $TEST_PHP_SQL_PWD - travis_retry docker exec client python ./test/functional/setup/run_ksp.py -server $TEST_PHP_SQL_SERVER -dbname $PDOSQLSRV_DBNAME -uid $TEST_PHP_SQL_UID -pwd $TEST_PHP_SQL_PWD - travis_retry docker exec client php ./source/pdo_sqlsrv/run-tests.php ./test/functional/pdo_sqlsrv/*.phpt diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql index fcdaba6e..6cff1116 100644 --- a/Dockerfile-msphpsql +++ b/Dockerfile-msphpsql @@ -44,20 +44,9 @@ RUN pip install --upgrade pip && pip install cpp-coveralls #Either Install git / download zip (One can see other strategies : https://ryanfb.github.io/etc/2015/07/29/git_strategies_for_docker.html ) #One option is to get source from zip file of repository. -<<<<<<< HEAD #another option is to copy source to build directory on image RUN mkdir -p $PHPSQLDIR COPY . $PHPSQLDIR -======= - -# another option is to copy source to build directory on image -RUN mkdir -p $PHPSQLDIR -COPY . $PHPSQLDIR - -# copy odbc header file to setup -COPY $PHPSQLDIR/source/shared/msodbcsql.h $PHPSQLDIR/test/functional/setup - ->>>>>>> 328286b... added checks for server version in the tests WORKDIR $PHPSQLDIR/source/ RUN chmod +x ./packagize.sh @@ -89,8 +78,5 @@ WORKDIR $PHPSQLDIR RUN chmod +x ./entrypoint.sh CMD /bin/bash ./entrypoint.sh -# copy odbc header file to setup -COPY $PHPSQLDIR/source/shared/msodbcsql.h $PHPSQLDIR/test/functional/setup - ENV REPORT_EXIT_STATUS 1 ENV TEST_PHP_EXECUTABLE /usr/bin/php \ No newline at end of file From fd1072d353ee3322d1b4842ab4196460f8c070c2 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 24 Aug 2017 11:33:07 -0700 Subject: [PATCH 41/53] reverted docker file and removed the commented out block in the test --- Dockerfile-msphpsql | 52 ++++++++++--------- .../pdo_connect_encrypted_ksp_errors.phpt | 17 +++--- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql index 6cff1116..81ff073f 100644 --- a/Dockerfile-msphpsql +++ b/Dockerfile-msphpsql @@ -4,29 +4,29 @@ FROM ubuntu:16.04 # Update Ubuntu Software repository RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && \ - apt-get -y install \ - apt-transport-https \ - apt-utils \ - autoconf \ - curl \ - g++ \ - gcc \ - git \ - lcov \ - libxml2-dev \ - locales \ - make \ - php7.0 \ - php7.0-dev \ - python-pip \ - re2c \ - unixodbc-dev \ - unzip && apt-get clean - + apt-get -y install \ + apt-transport-https \ + apt-utils \ + autoconf \ + curl \ + g++ \ + gcc \ + git \ + lcov \ + libxml2-dev \ + locales \ + make \ + php7.0 \ + php7.0-dev \ + python-pip \ + re2c \ + unixodbc-dev \ + unzip && apt-get clean + ARG PHPSQLDIR=/REPO/msphpsql-dev ENV TEST_PHP_SQL_SERVER sql ENV TEST_PHP_SQL_UID sa -ENV TEST_PHP_SQL_PWD Password123 +ENV TEST_PHP_SQL_PWD Password12@ # set locale to utf-8 RUN locale-gen en_US.UTF-8 @@ -36,7 +36,10 @@ ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8' RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list +#RUN echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/mssql-ubuntu-xenial-release/ xenial main" > /etc/apt/sources.list.d/mssqlpreview.list +#RUN apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893 RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql mssql-tools + ENV PATH="/opt/mssql-tools/bin:${PATH}" #install coveralls @@ -44,9 +47,11 @@ RUN pip install --upgrade pip && pip install cpp-coveralls #Either Install git / download zip (One can see other strategies : https://ryanfb.github.io/etc/2015/07/29/git_strategies_for_docker.html ) #One option is to get source from zip file of repository. + #another option is to copy source to build directory on image RUN mkdir -p $PHPSQLDIR COPY . $PHPSQLDIR + WORKDIR $PHPSQLDIR/source/ RUN chmod +x ./packagize.sh @@ -74,9 +79,6 @@ RUN sed -i -e 's/TARGET_DATABASE/msphpsql_sqlsrv/g' MsSetup.inc RUN sed -i -e 's/TARGET_USERNAME/'"$TEST_PHP_SQL_UID"'/g' MsSetup.inc RUN sed -i -e 's/TARGET_PASSWORD/'"$TEST_PHP_SQL_PWD"'/g' MsSetup.inc -WORKDIR $PHPSQLDIR -RUN chmod +x ./entrypoint.sh -CMD /bin/bash ./entrypoint.sh - ENV REPORT_EXIT_STATUS 1 -ENV TEST_PHP_EXECUTABLE /usr/bin/php \ No newline at end of file +ENV TEST_PHP_EXECUTABLE /usr/bin/php + diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt index 3b1da719..4db4d60c 100644 --- a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt @@ -30,13 +30,12 @@ Fetch data from a prepopulated test table given a custom keystore provider $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; connect( $connectionInfo ); - // will comment these when it's ready to test with a real KSP - // echo("\nConnecting... with an invalid input to CEKeystoreProvider\n"); - // $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; - // $connectionInfo .= "CEKeystoreName = 1; "; - // $connectionInfo .= "CEKeystoreProvider = $ksp_path; "; - // $connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; "; - // connect( $connectionInfo ); + echo("\nConnecting... with an invalid input to CEKeystoreProvider\n"); + $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; + $connectionInfo .= "CEKeystoreName = 1; "; + $connectionInfo .= "CEKeystoreProvider = $ksp_path; "; + $connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; "; + connect( $connectionInfo ); echo("\nConnecting... with an empty path\n"); $connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; "; @@ -76,6 +75,10 @@ Fetch data from a prepopulated test table given a custom keystore provider Connecting... with column encryption Connected successfully with ColumnEncryption enabled and KSP specified. +Connecting... with an invalid input to CEKeystoreProvider +Failed to connect. +SQLSTATE[HY024]: [Microsoft][ODBC Driver 13 for SQL Server]Invalid attribute value + Connecting... with an empty path Failed to connect. SQLSTATE[IMSSP]: Invalid value for loading a custom keystore provider. From 48ba8d7b7f5db8c800859380bf6c067198071ff2 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 24 Aug 2017 13:34:26 -0700 Subject: [PATCH 42/53] added two more tests for KSP --- .../pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt | 54 ++++++++++++++++ .../pdo_connect_encrypted_ksp_errors.phpt | 2 +- .../sqlsrv/sqlsrv_connect_encrypted_ksp.phpt | 63 +++++++++++++++++++ 3 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp.phpt diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt new file mode 100644 index 00000000..d8cd00ad --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt @@ -0,0 +1,54 @@ +--TEST-- +Fetch data from a prepopulated test table given a custom keystore provider +--SKIPIF-- + +--FILE-- +getMessage() ); + echo "\n"; + } + + $tsql = "SELECT * FROM CustomKSPTestTable"; + $stmt = $conn->query($tsql); + while ($row = $stmt->fetch(PDO::FETCH_NUM)) + { + echo "c1=" . $row[0] . "\tc2=" . $row[1] . "\n"; + } + + $stmt = null; + $conn = null; + + echo "Done\n"; + +?> +--EXPECT-- +Connected successfully with ColumnEncryption enabled and KSP specified. +c1=1 c2=Sample data 0 for column 2 +c1=12 c2=Sample data 1 for column 2 +c1=23 c2=Sample data 2 for column 2 +c1=34 c2=Sample data 3 for column 2 +c1=45 c2=Sample data 4 for column 2 +c1=56 c2=Sample data 5 for column 2 +c1=67 c2=Sample data 6 for column 2 +c1=78 c2=Sample data 7 for column 2 +c1=89 c2=Sample data 8 for column 2 +c1=100 c2=Sample data 9 for column 2 +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt index 4db4d60c..6b838d25 100644 --- a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt @@ -1,5 +1,5 @@ --TEST-- -Fetch data from a prepopulated test table given a custom keystore provider +Connect using a custom keystore provider with some required inputs missing --SKIPIF-- --FILE-- diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp.phpt new file mode 100644 index 00000000..de86809c --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp.phpt @@ -0,0 +1,63 @@ +--TEST-- +Fetch data from a prepopulated test table given a custom keystore provider +--SKIPIF-- + +--FILE-- +$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ColumnEncryption"=>"enabled", + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreName"=>$ksp_name, + "CEKeystoreEncryptKey"=>$encrypt_key); + + $conn = sqlsrv_connect( $server, $connectionInfo ); + if( $conn === false ) + { + echo "Failed to connect.\n"; + print_r( sqlsrv_errors() ); + } + else + { + echo "Connected successfully with ColumnEncryption enabled.\n"; + } + + $tsql = "SELECT * FROM $ksp_test_table"; + $stmt = sqlsrv_prepare($conn, $tsql); + if (! sqlsrv_execute($stmt) ) + { + echo "Failed to fetch data.\n"; + print_r( sqlsrv_errors() ); + } + + // fetch data + while ($row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC )) + { + echo "c1=" . $row[0] . "\tc2=" . $row[1] . "\n"; + } + + sqlsrv_free_stmt($stmt); + sqlsrv_close($conn); + + echo "Done\n"; +?> +--EXPECT-- +Connected successfully with ColumnEncryption enabled. +c1=1 c2=Sample data 0 for column 2 +c1=12 c2=Sample data 1 for column 2 +c1=23 c2=Sample data 2 for column 2 +c1=34 c2=Sample data 3 for column 2 +c1=45 c2=Sample data 4 for column 2 +c1=56 c2=Sample data 5 for column 2 +c1=67 c2=Sample data 6 for column 2 +c1=78 c2=Sample data 7 for column 2 +c1=89 c2=Sample data 8 for column 2 +c1=100 c2=Sample data 9 for column 2 +Done \ No newline at end of file From 84ff1648b02227b9d9ad7d0aa9d205c052abd93d Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 24 Aug 2017 14:54:52 -0700 Subject: [PATCH 43/53] minor modifications to build_ksp.py --- test/functional/setup/build_ksp.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test/functional/setup/build_ksp.py b/test/functional/setup/build_ksp.py index 27b07997..19655e0d 100644 --- a/test/functional/setup/build_ksp.py +++ b/test/functional/setup/build_ksp.py @@ -23,14 +23,12 @@ import os import platform import argparse -# This returns the path to the batch file vcvarsall.bat that comes with Visual Studio -def getCompilerPath(): - root_dir = 'C:' + os.sep - return os.path.join(root_dir, "Program Files (x86)", "Microsoft Visual Studio 14.0", "VC", "vcvarsall.bat") - -# This creates a batch file to compile myKSP.c into a dll, according to *arch* (either x86 or x64) +# This creates a batch *filename*, which compiles a C program according to +# *command* and *arch* (either x86 or x64) def create_batch_file(arch, filename, command): - vcvarsall = getCompilerPath() + root_dir = 'C:' + os.sep + vcvarsall = os.path.join(root_dir, "Program Files (x86)", "Microsoft Visual Studio 14.0", "VC", "vcvarsall.bat") + try: file = open(filename, 'w') file.write('@ECHO OFF' + os.linesep) From ddcb358368c0f6e314db80c7b362300a1f26b8b5 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 25 Aug 2017 16:12:16 -0700 Subject: [PATCH 44/53] added 2 more columns to ksp test table --- source/shared/core_sqlsrv.h | 16 +++++------ .../pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt | 24 ++++++++-------- test/functional/setup/ksp_app.c | 28 ++++++++++++++++--- .../sqlsrv/sqlsrv_connect_encrypted_ksp.phpt | 27 +++++++++--------- 4 files changed, 58 insertions(+), 37 deletions(-) diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 6a4c4235..361731ce 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1046,16 +1046,16 @@ struct stmt_option; // This holds the various details of column encryption. struct col_encryption_option { - bool enabled; // column encryption enabled, false by default - zval_auto_ptr ksp_name; // keystore provider name - zval_auto_ptr ksp_path; // keystore provider path to the dynamically linked libary (either a *.dll or *.so) - zval_auto_ptr ksp_encrypt_key; // the encryption key used to configure the keystore provider - size_t key_size; // the length of ksp_encrypt_key without the NULL terminator + bool enabled; // column encryption enabled, false by default + zval_auto_ptr ksp_name; // keystore provider name + zval_auto_ptr ksp_path; // keystore provider path to the dynamically linked libary (either a *.dll or *.so) + zval_auto_ptr ksp_encrypt_key; // the encryption key used to configure the keystore provider + size_t key_size; // the length of ksp_encrypt_key without the NULL terminator bool ksp_required; // a keystore provider is required to enable column encryption, false by default - col_encryption_option() : enabled( false ), key_size ( 0 ), ksp_required( false ) - { - } + col_encryption_option() : enabled( false ), key_size ( 0 ), ksp_required( false ) + { + } }; // *** connection resource structure *** diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt index d8cd00ad..f7759501 100644 --- a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt @@ -1,7 +1,7 @@ --TEST-- Fetch data from a prepopulated test table given a custom keystore provider --SKIPIF-- - + --FILE-- query($tsql); while ($row = $stmt->fetch(PDO::FETCH_NUM)) { - echo "c1=" . $row[0] . "\tc2=" . $row[1] . "\n"; + echo "c1=" . $row[0] . "\tc2=" . $row[1] . "\tc3=" . $row[2] . "\tc4=" . $row[3] . "\n"; } $stmt = null; @@ -41,14 +41,14 @@ Fetch data from a prepopulated test table given a custom keystore provider ?> --EXPECT-- Connected successfully with ColumnEncryption enabled and KSP specified. -c1=1 c2=Sample data 0 for column 2 -c1=12 c2=Sample data 1 for column 2 -c1=23 c2=Sample data 2 for column 2 -c1=34 c2=Sample data 3 for column 2 -c1=45 c2=Sample data 4 for column 2 -c1=56 c2=Sample data 5 for column 2 -c1=67 c2=Sample data 6 for column 2 -c1=78 c2=Sample data 7 for column 2 -c1=89 c2=Sample data 8 for column 2 -c1=100 c2=Sample data 9 for column 2 +c1=1 c2=Sample data 0 for column 2 c3=abc c4=2017-08-10 +c1=12 c2=Sample data 1 for column 2 c3=bcd c4=2017-08-11 +c1=23 c2=Sample data 2 for column 2 c3=cde c4=2017-08-12 +c1=34 c2=Sample data 3 for column 2 c3=def c4=2017-08-13 +c1=45 c2=Sample data 4 for column 2 c3=efg c4=2017-08-14 +c1=56 c2=Sample data 5 for column 2 c3=fgh c4=2017-08-15 +c1=67 c2=Sample data 6 for column 2 c3=ghi c4=2017-08-16 +c1=78 c2=Sample data 7 for column 2 c3=hij c4=2017-08-17 +c1=89 c2=Sample data 8 for column 2 c3=ijk c4=2017-08-18 +c1=100 c2=Sample data 9 for column 2 c3=jkl c4=2017-08-19 Done \ No newline at end of file diff --git a/test/functional/setup/ksp_app.c b/test/functional/setup/ksp_app.c index 74d22e37..882fe3d5 100644 --- a/test/functional/setup/ksp_app.c +++ b/test/functional/setup/ksp_app.c @@ -1,7 +1,7 @@ /****************************************************************************** Example application for demonstration of custom keystore provider usage - Windows: compile with cl /MD ksp_app.c /link odbc32.lib + Windows: compile with cl /MD ksp_app.c /link odbc32.lib /out:ksp_app.exe Linux/mac: compile with gcc -o ksp_app -fshort-wchar ksp_app.c -lodbc -ldl usage: kspapp connstr @@ -181,13 +181,15 @@ FoundProv: void populateTestTable(SQLHDBC dbc, SQLHSTMT stmt) { SQLRETURN rc; - int i; + int i, j; /* Create a table with encrypted columns */ { static char *tableSql = "CREATE TABLE CustomKSPTestTable (" "c1 int," - "c2 varchar(255) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256'))"; + "c2 varchar(255) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256')," + "c3 char(5) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256')," + "c4 date ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256'))"; printf("Create table: %s\n", tableSql); SQLExecDirect(stmt, tableSql, SQL_NTS); } @@ -213,14 +215,32 @@ void populateTestTable(SQLHDBC dbc, SQLHSTMT stmt) { int c1; char c2[256]; + char c3[6]; + SQL_DATE_STRUCT date; + SQLLEN cbdate; rc = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &c1, 0, 0); checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT); rc = SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 255, 0, c2, 255, 0); checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT); + rc = SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 5, 0, c3, 5, 0); + checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT); + checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT); + cbdate = sizeof(SQL_DATE_STRUCT); + rc = SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_TYPE_DATE, SQL_TYPE_DATE, 10, 0, &date, 0, &cbdate); + checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT); + + date.year = 2017; + date.month = 8; for (i = 0; i < 10; i++) { + date.day = i + 10; + c1 = i * 10 + i + 1; sprintf(c2, "Sample data %d for column 2", i); - rc = SQLExecDirect(stmt, "INSERT INTO CustomKSPTestTable (c1, c2) values (?, ?)", SQL_NTS); + for (j = 0; j < 3; j++) { + c3[j] = 'a' + i + j; + } + c3[3] = '\0'; + rc = SQLExecDirect(stmt, "INSERT INTO CustomKSPTestTable (c1, c2, c3, c4) values (?, ?, ?, ?)", SQL_NTS); checkRC(rc, "Inserting rows query", 10, stmt, SQL_HANDLE_STMT); } printf("(Encrypted) data has been inserted into CustomKSPTestTable. You may inspect the data now.\n"); diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp.phpt index de86809c..c0ab68ed 100644 --- a/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp.phpt +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp.phpt @@ -1,7 +1,7 @@ --TEST-- Fetch data from a prepopulated test table given a custom keystore provider --SKIPIF-- - + --FILE-- "enabled", "CEKeystoreProvider"=>$ksp_path, "CEKeystoreName"=>$ksp_name, - "CEKeystoreEncryptKey"=>$encrypt_key); + "CEKeystoreEncryptKey"=>$encrypt_key, + 'ReturnDatesAsStrings'=>true); $conn = sqlsrv_connect( $server, $connectionInfo ); if( $conn === false ) @@ -40,7 +41,7 @@ Fetch data from a prepopulated test table given a custom keystore provider // fetch data while ($row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC )) { - echo "c1=" . $row[0] . "\tc2=" . $row[1] . "\n"; + echo "c1=" . $row[0] . "\tc2=" . $row[1] . "\tc3=" . $row[2] . "\tc4=" . $row[3] . "\n" ; } sqlsrv_free_stmt($stmt); @@ -50,14 +51,14 @@ Fetch data from a prepopulated test table given a custom keystore provider ?> --EXPECT-- Connected successfully with ColumnEncryption enabled. -c1=1 c2=Sample data 0 for column 2 -c1=12 c2=Sample data 1 for column 2 -c1=23 c2=Sample data 2 for column 2 -c1=34 c2=Sample data 3 for column 2 -c1=45 c2=Sample data 4 for column 2 -c1=56 c2=Sample data 5 for column 2 -c1=67 c2=Sample data 6 for column 2 -c1=78 c2=Sample data 7 for column 2 -c1=89 c2=Sample data 8 for column 2 -c1=100 c2=Sample data 9 for column 2 +c1=1 c2=Sample data 0 for column 2 c3=abc c4=2017-08-10 +c1=12 c2=Sample data 1 for column 2 c3=bcd c4=2017-08-11 +c1=23 c2=Sample data 2 for column 2 c3=cde c4=2017-08-12 +c1=34 c2=Sample data 3 for column 2 c3=def c4=2017-08-13 +c1=45 c2=Sample data 4 for column 2 c3=efg c4=2017-08-14 +c1=56 c2=Sample data 5 for column 2 c3=fgh c4=2017-08-15 +c1=67 c2=Sample data 6 for column 2 c3=ghi c4=2017-08-16 +c1=78 c2=Sample data 7 for column 2 c3=hij c4=2017-08-17 +c1=89 c2=Sample data 8 for column 2 c3=ijk c4=2017-08-18 +c1=100 c2=Sample data 9 for column 2 c3=jkl c4=2017-08-19 Done \ No newline at end of file From d874f3b0dfd91e42aa71c67882131a090d281fa4 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 25 Aug 2017 16:28:24 -0700 Subject: [PATCH 45/53] modified the tests to also check sql server version --- test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt | 2 +- test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp.phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt index f7759501..868e8b79 100644 --- a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt @@ -1,7 +1,7 @@ --TEST-- Fetch data from a prepopulated test table given a custom keystore provider --SKIPIF-- - + --FILE-- + --FILE-- Date: Tue, 29 Aug 2017 13:42:15 -0700 Subject: [PATCH 46/53] conflicts in msodbcsql --- source/pdo_sqlsrv/pdo_dbh.cpp | 72 ++++++++++++------------ source/shared/core_conn.cpp | 6 +- source/sqlsrv/conn.cpp | 90 +++++++++++++++--------------- test/functional/setup/build_ksp.py | 44 +++++++-------- test/functional/setup/ksp_app.c | 15 +++-- 5 files changed, 116 insertions(+), 111 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 94c9b074..b86bec0e 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -225,42 +225,42 @@ const connection_option PDO_CONN_OPTS[] = { CONN_ATTR_BOOL, conn_null_func::func }, - { - PDOConnOptionNames::ColumnEncryption, - sizeof(PDOConnOptionNames::ColumnEncryption), - SQLSRV_CONN_OPTION_COLUMNENCRYPTION, - ODBCConnOptions::ColumnEncryption, - sizeof(ODBCConnOptions::ColumnEncryption), - CONN_ATTR_STRING, - column_encryption_set_func::func - }, - { - PDOConnOptionNames::CEKeystoreProvider, - sizeof(PDOConnOptionNames::CEKeystoreProvider), - SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER, - ODBCConnOptions::CEKeystoreProvider, - sizeof(ODBCConnOptions::CEKeystoreProvider), - CONN_ATTR_STRING, - ce_ksp_provider_set_func::func - }, - { - PDOConnOptionNames::CEKeystoreName, - sizeof(PDOConnOptionNames::CEKeystoreName), - SQLSRV_CONN_OPTION_CEKEYSTORE_NAME, - ODBCConnOptions::CEKeystoreName, - sizeof(ODBCConnOptions::CEKeystoreName), - CONN_ATTR_STRING, - ce_ksp_provider_set_func::func - }, - { - PDOConnOptionNames::CEKeystoreEncryptKey, - sizeof(PDOConnOptionNames::CEKeystoreEncryptKey), - SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY, - ODBCConnOptions::CEKeystoreEncryptKey, - sizeof(ODBCConnOptions::CEKeystoreEncryptKey), - CONN_ATTR_STRING, - ce_ksp_provider_set_func::func - }, + { + PDOConnOptionNames::ColumnEncryption, + sizeof(PDOConnOptionNames::ColumnEncryption), + SQLSRV_CONN_OPTION_COLUMNENCRYPTION, + ODBCConnOptions::ColumnEncryption, + sizeof(ODBCConnOptions::ColumnEncryption), + CONN_ATTR_STRING, + column_encryption_set_func::func + }, + { + PDOConnOptionNames::CEKeystoreProvider, + sizeof(PDOConnOptionNames::CEKeystoreProvider), + SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER, + ODBCConnOptions::CEKeystoreProvider, + sizeof(ODBCConnOptions::CEKeystoreProvider), + CONN_ATTR_STRING, + ce_ksp_provider_set_func::func + }, + { + PDOConnOptionNames::CEKeystoreName, + sizeof(PDOConnOptionNames::CEKeystoreName), + SQLSRV_CONN_OPTION_CEKEYSTORE_NAME, + ODBCConnOptions::CEKeystoreName, + sizeof(ODBCConnOptions::CEKeystoreName), + CONN_ATTR_STRING, + ce_ksp_provider_set_func::func + }, + { + PDOConnOptionNames::CEKeystoreEncryptKey, + sizeof(PDOConnOptionNames::CEKeystoreEncryptKey), + SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY, + ODBCConnOptions::CEKeystoreEncryptKey, + sizeof(ODBCConnOptions::CEKeystoreEncryptKey), + CONN_ATTR_STRING, + ce_ksp_provider_set_func::func + }, #ifdef _WIN32 { PDOConnOptionNames::ConnectRetryCount, diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index eae1d427..d8b31e11 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -779,7 +779,7 @@ void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) } // Column Encryption feature: if a custom keystore provider is specified, -// load and configure it when column encryption is enabled, but this step have +// load and configure it when column encryption is enabled, but this step has // to be executed after the connection has been established void load_configure_ksp( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) { @@ -826,7 +826,7 @@ void load_configure_ksp( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) wksp_name = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, ksp_name, name_len, &wname_len ); CHECK_CUSTOM_ERROR( wksp_name == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE ) { - throw core::CoreException(); + throw core::CoreException(); } pKsd->name = (wchar_t *) wksp_name.get(); @@ -839,7 +839,7 @@ void load_configure_ksp( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) core::SQLSetConnectAttr( conn, SQL_COPT_SS_CEKEYSTOREDATA, reinterpret_cast( pKsd ), SQL_IS_POINTER ); } -void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC ) +void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_len) const char* val, _Inout_ size_t val_len, _Inout_ std::string& conn_str TSRMLS_DC ) { // wrap a connection option in a quote. It is presumed that any character that need to be escaped will // be escaped, such as a closing }. diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 32afc87d..92698dd1 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -298,51 +298,51 @@ const connection_option SS_CONN_OPTS[] = { CONN_ATTR_STRING, conn_str_append_func::func }, - { - SSConnOptionNames::ConnectionPooling, - sizeof(SSConnOptionNames::ConnectionPooling), - SQLSRV_CONN_OPTION_CONN_POOLING, - ODBCConnOptions::ConnectionPooling, - sizeof(ODBCConnOptions::ConnectionPooling), - CONN_ATTR_BOOL, - conn_null_func::func - }, - { - SSConnOptionNames::ColumnEncryption, - sizeof(SSConnOptionNames::ColumnEncryption), - SQLSRV_CONN_OPTION_COLUMNENCRYPTION, - ODBCConnOptions::ColumnEncryption, - sizeof(ODBCConnOptions::ColumnEncryption), - CONN_ATTR_STRING, - column_encryption_set_func::func - }, - { - SSConnOptionNames::CEKeystoreProvider, - sizeof(SSConnOptionNames::CEKeystoreProvider), - SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER, - ODBCConnOptions::CEKeystoreProvider, - sizeof(ODBCConnOptions::CEKeystoreProvider), - CONN_ATTR_STRING, - ce_ksp_provider_set_func::func - }, - { - SSConnOptionNames::CEKeystoreName, - sizeof(SSConnOptionNames::CEKeystoreName), - SQLSRV_CONN_OPTION_CEKEYSTORE_NAME, - ODBCConnOptions::CEKeystoreName, - sizeof(ODBCConnOptions::CEKeystoreName), - CONN_ATTR_STRING, - ce_ksp_provider_set_func::func - }, - { - SSConnOptionNames::CEKeystoreEncryptKey, - sizeof(SSConnOptionNames::CEKeystoreEncryptKey), - SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY, - ODBCConnOptions::CEKeystoreEncryptKey, - sizeof(ODBCConnOptions::CEKeystoreEncryptKey), - CONN_ATTR_STRING, - ce_ksp_provider_set_func::func - }, + { + SSConnOptionNames::ConnectionPooling, + sizeof(SSConnOptionNames::ConnectionPooling), + SQLSRV_CONN_OPTION_CONN_POOLING, + ODBCConnOptions::ConnectionPooling, + sizeof(ODBCConnOptions::ConnectionPooling), + CONN_ATTR_BOOL, + conn_null_func::func + }, + { + SSConnOptionNames::ColumnEncryption, + sizeof(SSConnOptionNames::ColumnEncryption), + SQLSRV_CONN_OPTION_COLUMNENCRYPTION, + ODBCConnOptions::ColumnEncryption, + sizeof(ODBCConnOptions::ColumnEncryption), + CONN_ATTR_STRING, + column_encryption_set_func::func + }, + { + SSConnOptionNames::CEKeystoreProvider, + sizeof(SSConnOptionNames::CEKeystoreProvider), + SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER, + ODBCConnOptions::CEKeystoreProvider, + sizeof(ODBCConnOptions::CEKeystoreProvider), + CONN_ATTR_STRING, + ce_ksp_provider_set_func::func + }, + { + SSConnOptionNames::CEKeystoreName, + sizeof(SSConnOptionNames::CEKeystoreName), + SQLSRV_CONN_OPTION_CEKEYSTORE_NAME, + ODBCConnOptions::CEKeystoreName, + sizeof(ODBCConnOptions::CEKeystoreName), + CONN_ATTR_STRING, + ce_ksp_provider_set_func::func + }, + { + SSConnOptionNames::CEKeystoreEncryptKey, + sizeof(SSConnOptionNames::CEKeystoreEncryptKey), + SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY, + ODBCConnOptions::CEKeystoreEncryptKey, + sizeof(ODBCConnOptions::CEKeystoreEncryptKey), + CONN_ATTR_STRING, + ce_ksp_provider_set_func::func + }, #ifdef _WIN32 { SSConnOptionNames::ConnectRetryCount, diff --git a/test/functional/setup/build_ksp.py b/test/functional/setup/build_ksp.py index 19655e0d..fb524719 100644 --- a/test/functional/setup/build_ksp.py +++ b/test/functional/setup/build_ksp.py @@ -13,7 +13,7 @@ # ksp_app.c (or any equivalent) # msodbcsql.h (odbc header file) # -# Execution: Run with command line with required options +# Execution: Run with command line with optional options # py build_ksp.py --KSP myKSP --APP ksp_app # ############################################################################################# @@ -46,14 +46,14 @@ def create_batch_file(arch, filename, command): # This invokes the newly created batch file to compile the code, # according to *arch* (either x86 or x64). The batch file will be # removed afterwards -def compile_KSP_windows(arch, ksp_name): +def compile_KSP_windows(arch, ksp_src): output = 'myKSP' if arch == 'x64': output = output + arch + '.dll' else: output = output + '.dll' - command = 'cl {0}.c /LD /MD /link /out:'.format(ksp_name) + output + command = 'cl {0} /LD /MD /link /out:'.format(ksp_src) + output batchfile = 'build_KSP.bat' create_batch_file(arch, batchfile, command) os.system(batchfile) @@ -65,37 +65,37 @@ def compile_KSP_windows(arch, ksp_name): # Otherwise, this will compile the code and generate a .so file. # # Output: A custom keystore provider created -def compile_KSP(ksp_name): - print('Compiling ', ksp_name) +def compile_KSP(ksp_src): + print('Compiling ', ksp_src) if platform.system() == 'Windows': - compile_KSP_windows('x64', ksp_name) - compile_KSP_windows('x86', ksp_name) + compile_KSP_windows('x64', ksp_src) + compile_KSP_windows('x86', ksp_src) else: - os.system('gcc -fshort-wchar -fPIC -o myKSP.so -shared {0}.c'.format(ksp_name)) + os.system('gcc -fshort-wchar -fPIC -o myKSP.so -shared {0}'.format(ksp_src)) # This compiles ksp app, which assumes the existence of the .dll or the .so file. # # In Windows, a batch file is created in order to compile the code. -def configure_KSP(app_name): - print('Compiling ', app_name) +def configure_KSP(app_src): + print('Compiling ', app_src) if platform.system() == 'Windows': - command = 'cl /MD {0}.c /link odbc32.lib /out:ksp_app.exe'.format(app_name) + command = 'cl /MD {0} /link odbc32.lib /out:ksp_app.exe'.format(app_src) batchfile = 'build_app.bat' create_batch_file('x86', batchfile, command) os.system(batchfile) os.remove(batchfile) else: - os.system('gcc -o ksp_app -fshort-wchar {0}.c -lodbc -ldl'.format(app_name)) + os.system('gcc -o ksp_app -fshort-wchar {0} -lodbc -ldl'.format(app_src)) ################################### Main Function ################################### if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('-ksp', '--KSPNAME', default='myKSP', help='The KSP (keystore provider)') - parser.add_argument('-app', '--APPNAME', default='ksp_app', help='The app that uses the KSP') + parser.add_argument('-ksp', '--KSPSRC', default='myKSP.c', help='The source file of KSP (keystore provider)') + parser.add_argument('-app', '--APPSRC', default='ksp_app.c', help='The source file for the app that uses the KSP') args = parser.parse_args() - ksp_name = args.KSPNAME - app_name = args.APPNAME + ksp_src = args.KSPSRC + app_src = args.APPSRC header = 'msodbcsql.h' cwd = os.getcwd() @@ -107,15 +107,15 @@ if __name__ == '__main__': if not os.path.exists(os.path.join(work_dir, header)): print('Error: {0} not found!'.format(header)) exit(1) - if not os.path.exists(os.path.join(work_dir, ksp_name + '.c')): - print('Error: {0}.c not found!'.format(ksp_name)) + if not os.path.exists(os.path.join(work_dir, ksp_src)): + print('Error: {0}.c not found!'.format(ksp_src)) exit(1) - if not os.path.exists(os.path.join(work_dir, app_name + '.c')): - print('Error: {0}.c not found!'.format(app_name)) + if not os.path.exists(os.path.join(work_dir, app_src)): + print('Error: {0}.c not found!'.format(app_src)) exit(1) - compile_KSP(ksp_name) - configure_KSP(app_name) + compile_KSP(ksp_src) + configure_KSP(app_src) os.chdir(cwd) diff --git a/test/functional/setup/ksp_app.c b/test/functional/setup/ksp_app.c index 882fe3d5..9957fc12 100644 --- a/test/functional/setup/ksp_app.c +++ b/test/functional/setup/ksp_app.c @@ -72,6 +72,7 @@ int setKSPLibrary(SQLHSTMT stmt) { unsigned char CEK[32]; unsigned char *ECEK; unsigned short ECEKlen; + unsigned char foundProv = 0; int i; #ifdef _WIN32 HMODULE hProvLib; @@ -100,12 +101,16 @@ int setKSPLibrary(SQLHSTMT stmt) { return 3; } while (pKsp = *ppKsp++) { - if (!memcmp(KSPNAME, pKsp->Name, sizeof(KSPNAME))) - goto FoundProv; + if (!memcmp(KSPNAME, pKsp->Name, sizeof(KSPNAME))) { + foundProv = 1; + break; + } } - fprintf(stderr, "Could not find provider in the library\n"); - return 4; -FoundProv: + if (! foundProv) { + fprintf(stderr, "Could not find provider in the library\n"); + return 4; + } + if (pKsp->Init && !pKsp->Init(&ctx, postKspError)) { fprintf(stderr, "Could not initialize provider\n"); return 5; From 61ee5abb92aefe060913b5b21cfa7c2c3ea84d0e Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 29 Aug 2017 12:52:01 -0700 Subject: [PATCH 47/53] modified skipif and code based on review --- source/shared/core_conn.cpp | 3 +- source/shared/core_sqlsrv.h | 6 ++-- .../pdo_sqlsrv/skipif_server_old.inc | 24 ++++++++++---- test/functional/sqlsrv/skipif_server_old.inc | 33 ++++++++++++++----- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index d8b31e11..6666df0f 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -809,14 +809,13 @@ void load_configure_ksp( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) char* ksp_name = Z_STRVAL_P( conn->ce_option.ksp_name ); char* ksp_path = Z_STRVAL_P( conn->ce_option.ksp_path ); unsigned int name_len = Z_STRLEN_P( conn->ce_option.ksp_name ); - unsigned int path_len = Z_STRLEN_P( conn->ce_option.ksp_path ); unsigned int key_size = conn->ce_option.key_size; sqlsrv_malloc_auto_ptr ksp_data; ksp_data = reinterpret_cast( sqlsrv_malloc( sizeof( CEKEYSTOREDATA ) + key_size ) ); - CEKEYSTOREDATA *pKsd = (CEKEYSTOREDATA*) ksp_data.get(); + CEKEYSTOREDATA *pKsd = reinterpret_cast( ksp_data.get() ); pKsd->dataSize = key_size; diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 361731ce..ff5e1024 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -2434,10 +2434,8 @@ struct ce_ksp_provider_set_func { static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC) { - CHECK_CUSTOM_ERROR( Z_TYPE_P( value ) != IS_STRING, conn, SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, option->odbc_name ) { - throw core::CoreException(); - } - + SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "Wrong zval type for this keyword" ) + size_t value_len = Z_STRLEN_P( value ); CHECK_CUSTOM_ERROR( value_len == 0, conn, SQLSRV_ERROR_KEYSTORE_INVALID_VALUE ) { diff --git a/test/functional/pdo_sqlsrv/skipif_server_old.inc b/test/functional/pdo_sqlsrv/skipif_server_old.inc index 0e1ff9f7..55c5e3ce 100644 --- a/test/functional/pdo_sqlsrv/skipif_server_old.inc +++ b/test/functional/pdo_sqlsrv/skipif_server_old.inc @@ -7,13 +7,25 @@ require_once( "MsSetup.inc" ); $conn = new PDO("sqlsrv:server = $server;", $uid, $pwd ); if( ! $conn ) { - die( "skip - could not connect during SKIPIF." ); + echo ( "Error: could not connect during SKIPIF!" ); } - -$attr = $conn->getAttribute(constant('PDO::ATTR_SERVER_VERSION')); -$version = substr($attr, 0, 2); -if ($version < 13) +else { - die( "skip - feature not supported in this version of SQL Server." ); + $attr = $conn->getAttribute(constant('PDO::ATTR_SERVER_VERSION')); + $version = substr($attr, 0, 2); + if ($version < 13) + { + // older than SQL Server 2016 + die( "skip - feature not supported in this version of SQL Server." ); + } + + // check ODBC driver version + $attr = $conn->getAttribute(constant('PDO::ATTR_CLIENT_VERSION')); + $version = substr($attr['DriverVer'], 0, 2); + if ($version < 13) + { + // older than ODBC 13 + die( "skip - feature not supported in this version of ODBC driver." ); + } } ?> \ No newline at end of file diff --git a/test/functional/sqlsrv/skipif_server_old.inc b/test/functional/sqlsrv/skipif_server_old.inc index 3ee03362..42614bd4 100644 --- a/test/functional/sqlsrv/skipif_server_old.inc +++ b/test/functional/sqlsrv/skipif_server_old.inc @@ -9,17 +9,32 @@ $connectionInfo = array( "UID"=>$userName, "PWD"=>$userPassword ); $conn = sqlsrv_connect( $server, $connectionInfo ); if( ! $conn ) { - die( "skip - could not connect during SKIPIF." ); + echo ( "Error: could not connect during SKIPIF!" ); } - -$server_info = sqlsrv_server_info( $conn ); -if( $server_info ) -{ - // check SQL Server version - $version = substr($server_info['SQLServerVersion'], 0, 2); - if ($version < 13) +else +{ + $server_info = sqlsrv_server_info( $conn ); + if( $server_info ) + { + // check SQL Server version + $version = substr($server_info['SQLServerVersion'], 0, 2); + if ($version < 13) + { + // older than SQL Server 2016 + die( "skip - feature not supported in this version of SQL Server." ); + } + } + + $client_info = sqlsrv_client_info( $conn ); + if( $client_info ) { - die( "skip - feature not supported in this version of SQL Server." ); + // check ODBC driver version + $version = substr($client_info['DriverVer'], 0, 2); + if ($version < 13) + { + // older than ODBC 13 + die( "skip - feature not supported in this version of ODBC driver." ); + } } } ?> \ No newline at end of file From 23af0446b77ebaccda2640ec3bb88358a8afbcf3 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 29 Aug 2017 13:44:55 -0700 Subject: [PATCH 48/53] fixed travis.yml --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b52af53c..3f59d61c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,8 +19,6 @@ env: - TEST_PHP_SQL_UID=sa - TEST_PHP_SQL_PWD=Password12@ ->>>>>>> 22838b1... added KSP related code and scripts to enable testing KSP - before_install: - docker pull microsoft/mssql-server-linux From c96090da2288f2198147a3bc018ede051aa6dd9d Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 29 Aug 2017 14:24:05 -0700 Subject: [PATCH 49/53] synced travis / docker with dev --- .travis.yml | 2 +- Dockerfile-msphpsql | 52 ++++++++++++++++++++++----------------------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3f59d61c..dfe1b0f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ env: - SQLSRV_DBNAME=msphpsql_sqlsrv - PDOSQLSRV_DBNAME=msphpsql_pdosqlsrv - TEST_PHP_SQL_UID=sa - - TEST_PHP_SQL_PWD=Password12@ + - TEST_PHP_SQL_PWD=Password123 before_install: - docker pull microsoft/mssql-server-linux diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql index 81ff073f..6cff1116 100644 --- a/Dockerfile-msphpsql +++ b/Dockerfile-msphpsql @@ -4,29 +4,29 @@ FROM ubuntu:16.04 # Update Ubuntu Software repository RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && \ - apt-get -y install \ - apt-transport-https \ - apt-utils \ - autoconf \ - curl \ - g++ \ - gcc \ - git \ - lcov \ - libxml2-dev \ - locales \ - make \ - php7.0 \ - php7.0-dev \ - python-pip \ - re2c \ - unixodbc-dev \ - unzip && apt-get clean - + apt-get -y install \ + apt-transport-https \ + apt-utils \ + autoconf \ + curl \ + g++ \ + gcc \ + git \ + lcov \ + libxml2-dev \ + locales \ + make \ + php7.0 \ + php7.0-dev \ + python-pip \ + re2c \ + unixodbc-dev \ + unzip && apt-get clean + ARG PHPSQLDIR=/REPO/msphpsql-dev ENV TEST_PHP_SQL_SERVER sql ENV TEST_PHP_SQL_UID sa -ENV TEST_PHP_SQL_PWD Password12@ +ENV TEST_PHP_SQL_PWD Password123 # set locale to utf-8 RUN locale-gen en_US.UTF-8 @@ -36,10 +36,7 @@ ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8' RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list -#RUN echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/mssql-ubuntu-xenial-release/ xenial main" > /etc/apt/sources.list.d/mssqlpreview.list -#RUN apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893 RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql mssql-tools - ENV PATH="/opt/mssql-tools/bin:${PATH}" #install coveralls @@ -47,11 +44,9 @@ RUN pip install --upgrade pip && pip install cpp-coveralls #Either Install git / download zip (One can see other strategies : https://ryanfb.github.io/etc/2015/07/29/git_strategies_for_docker.html ) #One option is to get source from zip file of repository. - #another option is to copy source to build directory on image RUN mkdir -p $PHPSQLDIR COPY . $PHPSQLDIR - WORKDIR $PHPSQLDIR/source/ RUN chmod +x ./packagize.sh @@ -79,6 +74,9 @@ RUN sed -i -e 's/TARGET_DATABASE/msphpsql_sqlsrv/g' MsSetup.inc RUN sed -i -e 's/TARGET_USERNAME/'"$TEST_PHP_SQL_UID"'/g' MsSetup.inc RUN sed -i -e 's/TARGET_PASSWORD/'"$TEST_PHP_SQL_PWD"'/g' MsSetup.inc -ENV REPORT_EXIT_STATUS 1 -ENV TEST_PHP_EXECUTABLE /usr/bin/php +WORKDIR $PHPSQLDIR +RUN chmod +x ./entrypoint.sh +CMD /bin/bash ./entrypoint.sh +ENV REPORT_EXIT_STATUS 1 +ENV TEST_PHP_EXECUTABLE /usr/bin/php \ No newline at end of file From 86bbf0b588064e29208f14bdeaa88447a7bb8ab1 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 29 Aug 2017 16:26:21 -0700 Subject: [PATCH 50/53] added two new tests to retrieve encrypted data --- .../pdo_connect_encrypted_ksp_encrypted.phpt | 58 ++++++++++++++++ ...qlsrv_connect_encrypted_ksp_encrypted.phpt | 68 +++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_encrypted.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_encrypted.phpt diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_encrypted.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_encrypted.phpt new file mode 100644 index 00000000..55f9f6d3 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_encrypted.phpt @@ -0,0 +1,58 @@ +--TEST-- +Fetch encrypted data from a prepopulated test table given a custom keystore provider +--SKIPIF-- + +--FILE-- +getMessage() ); + echo "\n"; + } + + $tsql = "SELECT * FROM CustomKSPTestTable"; + $stmt = $conn->query($tsql); + while ($row = $stmt->fetch(PDO::FETCH_NUM)) + { + echo "c1=" . $row[0]; + echo "\tc2=" . bin2hex($row[1]); + echo "\tc3=" . bin2hex($row[2]); + echo "\tc4=" . bin2hex($row[3]); + echo "\n" ; + } + + $stmt = null; + $conn = null; + + echo "Done\n"; + +?> +--EXPECTREGEX-- +Connected successfully with ColumnEncryption disabled and KSP specified. +c1=1 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=12 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=23 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=34 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=45 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=56 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=67 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=78 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=89 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=100 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_encrypted.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_encrypted.phpt new file mode 100644 index 00000000..8c7bb49f --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_encrypted.phpt @@ -0,0 +1,68 @@ +--TEST-- +Fetch encrypted data from a prepopulated test table given a custom keystore provider +--SKIPIF-- + +--FILE-- +$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreName"=>$ksp_name, + "CEKeystoreEncryptKey"=>$encrypt_key, + 'ReturnDatesAsStrings'=>true); + + $conn = sqlsrv_connect( $server, $connectionInfo ); + if( $conn === false ) + { + echo "Failed to connect.\n"; + print_r( sqlsrv_errors() ); + } + else + { + echo "Connected successfully with ColumnEncryption disabled.\n"; + } + + $tsql = "SELECT * FROM $ksp_test_table"; + $stmt = sqlsrv_prepare($conn, $tsql); + if (! sqlsrv_execute($stmt) ) + { + echo "Failed to fetch data.\n"; + print_r( sqlsrv_errors() ); + } + + // fetch data + while ($row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC )) + { + // all columns should return binary data except the first column + echo "c1=" . $row[0]; + echo "\tc2=" . bin2hex($row[1]); + echo "\tc3=" . bin2hex($row[2]); + echo "\tc4=" . bin2hex($row[3]); + echo "\n" ; + } + + sqlsrv_free_stmt($stmt); + sqlsrv_close($conn); + + echo "Done\n"; +?> +--EXPECTREGEX-- +Connected successfully with ColumnEncryption disabled. +c1=1 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=12 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=23 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=34 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=45 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=56 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=67 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=78 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=89 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +c1=100 c2=[a-f0-9]+ c3=[a-f0-9]+ c4=[a-f0-9]+ +Done \ No newline at end of file From 39bb678814d9276f26385b043342ee282a17f8d4 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 29 Aug 2017 21:46:48 -0700 Subject: [PATCH 51/53] used skipif_server_old for ksp tests --- .../pdo_sqlsrv/pdo_connect_encrypted_ksp_encrypted.phpt | 2 +- .../sqlsrv/sqlsrv_connect_encrypted_ksp_encrypted.phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_encrypted.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_encrypted.phpt index 55f9f6d3..4e80c122 100644 --- a/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_encrypted.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_encrypted.phpt @@ -1,7 +1,7 @@ --TEST-- Fetch encrypted data from a prepopulated test table given a custom keystore provider --SKIPIF-- - + --FILE-- + --FILE-- Date: Wed, 30 Aug 2017 08:56:09 -0700 Subject: [PATCH 52/53] another sqlsrv ksp test for simple insertion, retrieval and update --- .../sqlsrv/sqlsrv_encrypted_patients_ksp.phpt | 248 ++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 test/functional/sqlsrv/sqlsrv_encrypted_patients_ksp.phpt diff --git a/test/functional/sqlsrv/sqlsrv_encrypted_patients_ksp.phpt b/test/functional/sqlsrv/sqlsrv_encrypted_patients_ksp.phpt new file mode 100644 index 00000000..d7d0f403 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_encrypted_patients_ksp.phpt @@ -0,0 +1,248 @@ +--TEST-- +Test simple insert, fetch and update with ColumnEncryption enabled and a custome keystore provider +--SKIPIF-- + +--FILE-- +PatientId . "\n"; + echo $obj->SSN . "\n"; + echo $obj->FirstName . "\n"; + echo $obj->LastName . "\n"; + echo $obj->BirthDate . "\n\n"; + } + } + + function SelectDataBuffered() + { + global $conn, $tablename; + + $stmt = sqlsrv_query($conn, "SELECT * FROM $tablename", array(), array("Scrollable"=>"buffered")); + + $row_count = sqlsrv_num_rows($stmt); + echo "\nRow count for result set is $row_count\n"; + + echo "First record=>\t"; + $row = sqlsrv_fetch($stmt, SQLSRV_SCROLL_FIRST); + $SSN = sqlsrv_get_field( $stmt, 1); + echo "SSN = $SSN \n"; + + echo "Next record=>\t"; + $row = sqlsrv_fetch($stmt, SQLSRV_SCROLL_NEXT); + $BirthDate = sqlsrv_get_field( $stmt, 4); + echo "BirthDate = $BirthDate \n"; + + echo "Last record=>\t"; + $row = sqlsrv_fetch($stmt, SQLSRV_SCROLL_LAST); + $LastName = sqlsrv_get_field( $stmt, 3); + echo "LastName = $LastName \n"; + } + + sqlsrv_configure( 'WarningsReturnAsErrors', 1 ); + sqlsrv_configure( 'LogSeverity', SQLSRV_LOG_SEVERITY_ALL ); + + require_once( 'MsSetup.inc' ); + require_once( 'AE_Ksp.inc' ); + + $ksp_path = getKSPpath(); + + $connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, + "ReturnDatesAsStrings"=>true, "ColumnEncryption"=>'Enabled', + "CEKeystoreProvider"=>$ksp_path, + "CEKeystoreName"=>$ksp_name, + "CEKeystoreEncryptKey"=>$encrypt_key); + + $conn = sqlsrv_connect( $server, $connectionInfo ); + if( $conn === false ) + { + echo "Failed to connect.\n"; + print_r( sqlsrv_errors() ); + } + else + { + echo "Connected successfully with ColumnEncryption enabled.\n"; + } + + $tablename = CreatePatientsTable(); + + InsertData('748-68-0245', 'Jeannette', 'McDonald', '2002-11-28'); + InsertData('795-73-9838', 'John', 'Doe', '2001-05-29'); + InsertData('456-12-5486', 'Jonathan', 'Wong', '1999-12-20'); + InsertData('156-45-5486', 'Marianne', 'Smith', '1997-03-04'); + + SelectData(); + + /////////////////////////////////////////// + echo "Update Patient Jonathan Wong...\n"; + $params = array(array('1999-12-31', null, null, SQLSRV_SQLTYPE_DATE), array('Chang', null, null, SQLSRV_SQLTYPE_NVARCHAR(50)), array('456-12-5486', null, null, SQLSRV_SQLTYPE_CHAR(11))); + + $tsql = "UPDATE Patients SET BirthDate = ?, LastName = ? WHERE SSN = ?"; + $stmt = sqlsrv_query($conn, $tsql, $params); + + if (! $stmt) + { + echo "Failed to update record\n"; + print_r( sqlsrv_errors() ); + } + + echo "Update his birthdate too...\n"; + $params = array(array('456-12-5486', null, null, SQLSRV_SQLTYPE_CHAR(11))); + $tsql = "SELECT SSN, FirstName, LastName, BirthDate FROM Patients WHERE SSN = ?"; + $stmt = sqlsrv_query($conn, $tsql, $params); + if (! $stmt) + { + echo "Failed to select with a WHERE clause\n"; + print_r( sqlsrv_errors() ); + } + else + { + $obj = sqlsrv_fetch_object( $stmt ); + + echo "BirthDate updated for $obj->FirstName:\n"; + echo $obj->SSN . "\n"; + echo $obj->FirstName . "\n"; + echo $obj->LastName . "\n"; + echo $obj->BirthDate . "\n\n"; + } + + /////////////////////////////////////////// + $procName = '#phpAEProc1'; + $spArgs = "@p1 INT, @p2 DATE OUTPUT"; + $spCode = "SET @p2 = ( SELECT [BirthDate] FROM Patients WHERE [PatientId] = @p1 )"; + $stmt = sqlsrv_query($conn, "CREATE PROC [$procName] ($spArgs) AS BEGIN $spCode END"); + sqlsrv_free_stmt($stmt); + + $callResult = '1900-01-01'; + $params = array( array( 1, SQLSRV_PARAM_IN ), array( &$callResult, SQLSRV_PARAM_OUT, null, SQLSRV_SQLTYPE_DATE)); + $callArgs = "?, ?"; + $stmt = sqlsrv_query($conn, "{ CALL [$procName] ($callArgs)}", $params); + if (! $stmt ) + { + print_r( sqlsrv_errors() ); + } + else + { + echo "BirthDate for the first record is: $callResult\n"; + } + + /////////////////////////////////////////// + $procName = '#phpAEProc2'; + $spArgs = "@p1 INT, @p2 CHAR(11) OUTPUT"; + $spCode = "SET @p2 = ( SELECT [SSN] FROM Patients WHERE [PatientId] = @p1 )"; + $stmt = sqlsrv_query($conn, "CREATE PROC [$procName] ($spArgs) AS BEGIN $spCode END"); + sqlsrv_free_stmt($stmt); + + $callResult = '000-00-0000'; + $params = array( array( 1, SQLSRV_PARAM_IN ), array( &$callResult, SQLSRV_PARAM_OUT, null, SQLSRV_SQLTYPE_CHAR(11))); + $callArgs = "?, ?"; + $stmt = sqlsrv_query($conn, "{ CALL [$procName] ($callArgs)}", $params); + if (! $stmt ) + { + print_r( sqlsrv_errors() ); + } + else + { + echo "SSN for the first record is: $callResult\n"; + } + + SelectDataBuffered(); + + echo "\nDone\n"; +?> +--EXPECT-- +Connected successfully with ColumnEncryption enabled. +1 +748-68-0245 +Jeannette +McDonald +2002-11-28 + +2 +795-73-9838 +John +Doe +2001-05-29 + +3 +456-12-5486 +Jonathan +Wong +1999-12-20 + +4 +156-45-5486 +Marianne +Smith +1997-03-04 + +Update Patient Jonathan Wong... +Update his birthdate too... +BirthDate updated for Jonathan: +456-12-5486 +Jonathan +Chang +1999-12-31 + +BirthDate for the first record is: 2002-11-28 +SSN for the first record is: 748-68-0245 + +Row count for result set is 4 +First record=> SSN = 748-68-0245 +Next record=> BirthDate = 2001-05-29 +Last record=> LastName = Smith + +Done \ No newline at end of file From d0781c01e3671de7f8cacb0ac6ef1daae3a2db1b Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 30 Aug 2017 11:15:13 -0700 Subject: [PATCH 53/53] modified sqlsrv_connect_encrypted_ksp_errors.phpt based on review --- .../sqlsrv_connect_encrypted_ksp_errors.phpt | 72 +++++++------------ 1 file changed, 26 insertions(+), 46 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt index be6204e0..51e10ee5 100644 --- a/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt @@ -12,7 +12,12 @@ Connect using a custom keystore provider with some required inputs missing { echo "Failed to connect.\n"; $errors = sqlsrv_errors(); - print_r( $errors[0] ); + foreach ( $errors[0] as $key => $error ) + { + if( is_string( $key ) ) + echo "[$key] => $error\n"; + } + echo "\n"; } else { @@ -92,59 +97,34 @@ Connecting... with column encryption Connected successfully with ColumnEncryption enabled. Connecting... with an invalid input to CEKeystoreProvider Failed to connect. -Array -( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -33 - [code] => -33 - [2] => Invalid value type for option CEKeystoreProvider was specified. String type was expected. - [message] => Invalid value type for option CEKeystoreProvider was specified. String type was expected. -) +[SQLSTATE] => IMSSP +[code] => -33 +[message] => Invalid value type for option CEKeystoreProvider was specified. String type was expected. + Connecting... with an empty path Failed to connect. -Array -( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -104 - [code] => -104 - [2] => Invalid value for loading a custom keystore provider. - [message] => Invalid value for loading a custom keystore provider. -) +[SQLSTATE] => IMSSP +[code] => -104 +[message] => Invalid value for loading a custom keystore provider. + Connecting... without a name Failed to connect. -Array -( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -101 - [code] => -101 - [2] => The name of the custom keystore provider is missing. - [message] => The name of the custom keystore provider is missing. -) +[SQLSTATE] => IMSSP +[code] => -101 +[message] => The name of the custom keystore provider is missing. + Connecting... with an empty name Failed to connect. -Array -( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -104 - [code] => -104 - [2] => Invalid value for loading a custom keystore provider. - [message] => Invalid value for loading a custom keystore provider. -) +[SQLSTATE] => IMSSP +[code] => -104 +[message] => Invalid value for loading a custom keystore provider. + Connecting... without a key Failed to connect. -Array -( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -103 - [code] => -103 - [2] => The encryption key for the custom keystore provider is missing. - [message] => The encryption key for the custom keystore provider is missing. -) +[SQLSTATE] => IMSSP +[code] => -103 +[message] => The encryption key for the custom keystore provider is missing. + Connecting... with all required inputs Connected successfully with ColumnEncryption enabled. Done \ No newline at end of file