Merge pull request #511 from yitam/KSProvider
Added support to load a custom keystore provider for Column Encryption
This commit is contained in:
commit
9675958a10
10
.travis.yml
10
.travis.yml
|
@ -15,7 +15,9 @@ 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=Password123
|
||||
|
||||
before_install:
|
||||
- docker pull microsoft/mssql-server-linux
|
||||
|
@ -33,6 +35,12 @@ 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
|
||||
- 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
|
||||
- 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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
@ -230,6 +234,33 @@ const connection_option PDO_CONN_OPTS[] = {
|
|||
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,
|
||||
|
|
|
@ -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, {} }
|
||||
};
|
||||
|
||||
|
|
|
@ -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,6 +778,66 @@ void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC )
|
|||
conn->server_version = version_major;
|
||||
}
|
||||
|
||||
// Column Encryption feature: if a custom keystore provider is specified,
|
||||
// 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 )
|
||||
{
|
||||
// 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 key_size = conn->ce_option.key_size;
|
||||
|
||||
sqlsrv_malloc_auto_ptr<unsigned char> ksp_data;
|
||||
|
||||
ksp_data = reinterpret_cast<unsigned char*>( sqlsrv_malloc( sizeof( CEKEYSTOREDATA ) + key_size ) );
|
||||
|
||||
CEKEYSTOREDATA *pKsd = reinterpret_cast<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<SQLWCHAR> 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<SQLPOINTER>( pKsd ), SQL_IS_POINTER );
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -1046,10 +1046,14 @@ 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 )
|
||||
{
|
||||
}
|
||||
};
|
||||
|
@ -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,37 @@ 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)
|
||||
{
|
||||
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 ) {
|
||||
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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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";
|
||||
|
@ -296,10 +300,10 @@ const connection_option SS_CONN_OPTS[] = {
|
|||
},
|
||||
{
|
||||
SSConnOptionNames::ConnectionPooling,
|
||||
sizeof( SSConnOptionNames::ConnectionPooling ),
|
||||
sizeof(SSConnOptionNames::ConnectionPooling),
|
||||
SQLSRV_CONN_OPTION_CONN_POOLING,
|
||||
ODBCConnOptions::ConnectionPooling,
|
||||
sizeof( ODBCConnOptions::ConnectionPooling ),
|
||||
sizeof(ODBCConnOptions::ConnectionPooling),
|
||||
CONN_ATTR_BOOL,
|
||||
conn_null_func::func
|
||||
},
|
||||
|
@ -312,6 +316,33 @@ const connection_option SS_CONN_OPTS[] = {
|
|||
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,
|
||||
|
|
|
@ -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, {} }
|
||||
};
|
||||
|
|
25
test/functional/pdo_sqlsrv/AE_Ksp.inc
Normal file
25
test/functional/pdo_sqlsrv/AE_Ksp.inc
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
function getKSPpath()
|
||||
{
|
||||
$name = 'myKSP';
|
||||
|
||||
$dir_name = realpath(dirname(__FILE__));
|
||||
$ksp = $dir_name . DIRECTORY_SEPARATOR . $name;
|
||||
if ( strtoupper( substr( php_uname( 's' ), 0, 3 ) ) == 'WIN' ) {
|
||||
$arch = 'x64';
|
||||
if ( PHP_INT_SIZE == 4 ) // running 32 bit
|
||||
$arch = '';
|
||||
$ksp .= $arch . '.dll';
|
||||
}
|
||||
else
|
||||
$ksp .= '.so';
|
||||
|
||||
return $ksp;
|
||||
}
|
||||
|
||||
$ksp_name = 'MyCustomKSPName';
|
||||
$encrypt_key = 'LPKCWVD07N3RG98J0MBLG4H2';
|
||||
$ksp_test_table = 'CustomKSPTestTable';
|
||||
|
||||
?>
|
54
test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt
Normal file
54
test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp.phpt
Normal file
|
@ -0,0 +1,54 @@
|
|||
--TEST--
|
||||
Fetch data from a prepopulated test table given a custom keystore provider
|
||||
--SKIPIF--
|
||||
<?php require('skipif_server_old.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require( 'MsSetup.inc' );
|
||||
require( 'AE_Ksp.inc' );
|
||||
|
||||
$ksp_path = getKSPpath();
|
||||
|
||||
$connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled; ";
|
||||
$connectionInfo .= "CEKeystoreProvider = $ksp_path; ";
|
||||
$connectionInfo .= "CEKeystoreName = $ksp_name; ";
|
||||
$connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; ";
|
||||
|
||||
try
|
||||
{
|
||||
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
|
||||
echo "Connected successfully with ColumnEncryption enabled and KSP specified.\n";
|
||||
}
|
||||
catch( PDOException $e )
|
||||
{
|
||||
echo "Failed to connect.\n";
|
||||
print_r( $e->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] . "\tc3=" . $row[2] . "\tc4=" . $row[3] . "\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 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
|
|
@ -0,0 +1,58 @@
|
|||
--TEST--
|
||||
Fetch encrypted data from a prepopulated test table given a custom keystore provider
|
||||
--SKIPIF--
|
||||
<?php require('skipif_server_old.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require( 'MsSetup.inc' );
|
||||
require( 'AE_Ksp.inc' );
|
||||
|
||||
$ksp_path = getKSPpath();
|
||||
|
||||
$connectionInfo = "Database = $databaseName; ";
|
||||
$connectionInfo .= "CEKeystoreProvider = $ksp_path; ";
|
||||
$connectionInfo .= "CEKeystoreName = $ksp_name; ";
|
||||
$connectionInfo .= "CEKeystoreEncryptKey = $encrypt_key; ";
|
||||
|
||||
try
|
||||
{
|
||||
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
|
||||
echo "Connected successfully with ColumnEncryption disabled and KSP specified.\n";
|
||||
}
|
||||
catch( PDOException $e )
|
||||
{
|
||||
echo "Failed to connect.\n";
|
||||
print_r( $e->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
|
100
test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt
Normal file
100
test/functional/pdo_sqlsrv/pdo_connect_encrypted_ksp_errors.phpt
Normal file
|
@ -0,0 +1,100 @@
|
|||
--TEST--
|
||||
Connect using a custom keystore provider with some required inputs missing
|
||||
--SKIPIF--
|
||||
<?php require('skipif_server_old.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require( 'MsSetup.inc' );
|
||||
require( 'AE_Ksp.inc' );
|
||||
|
||||
function connect( $connectionInfo )
|
||||
{
|
||||
global $server, $uid, $pwd;
|
||||
|
||||
try
|
||||
{
|
||||
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
|
||||
echo "Connected successfully with ColumnEncryption enabled and KSP specified.\n";
|
||||
}
|
||||
catch( PDOException $e )
|
||||
{
|
||||
echo "Failed to connect.\n";
|
||||
print_r( $e->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
|
31
test/functional/pdo_sqlsrv/skipif_server_old.inc
Normal file
31
test/functional/pdo_sqlsrv/skipif_server_old.inc
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
if (!extension_loaded("pdo") || !extension_loaded('pdo_sqlsrv'))
|
||||
die("PDO driver cannot be loaded; skipping test.\n");
|
||||
|
||||
require_once( "MsSetup.inc" );
|
||||
$conn = new PDO("sqlsrv:server = $server;", $uid, $pwd );
|
||||
if( ! $conn )
|
||||
{
|
||||
echo ( "Error: could not connect during SKIPIF!" );
|
||||
}
|
||||
else
|
||||
{
|
||||
$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." );
|
||||
}
|
||||
}
|
||||
?>
|
122
test/functional/setup/build_ksp.py
Normal file
122
test/functional/setup/build_ksp.py
Normal file
|
@ -0,0 +1,122 @@
|
|||
#!/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 optional options
|
||||
# py build_ksp.py --KSP myKSP --APP ksp_app
|
||||
#
|
||||
#############################################################################################
|
||||
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
import argparse
|
||||
|
||||
# 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):
|
||||
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)
|
||||
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_src):
|
||||
output = 'myKSP'
|
||||
if arch == 'x64':
|
||||
output = output + arch + '.dll'
|
||||
else:
|
||||
output = output + '.dll'
|
||||
|
||||
command = 'cl {0} /LD /MD /link /out:'.format(ksp_src) + 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_src):
|
||||
print('Compiling ', ksp_src)
|
||||
if platform.system() == 'Windows':
|
||||
compile_KSP_windows('x64', ksp_src)
|
||||
compile_KSP_windows('x86', ksp_src)
|
||||
else:
|
||||
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_src):
|
||||
print('Compiling ', app_src)
|
||||
if platform.system() == 'Windows':
|
||||
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} -lodbc -ldl'.format(app_src))
|
||||
|
||||
################################### Main Function ###################################
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
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_src = args.KSPSRC
|
||||
app_src = args.APPSRC
|
||||
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)
|
||||
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_src)):
|
||||
print('Error: {0}.c not found!'.format(app_src))
|
||||
exit(1)
|
||||
|
||||
compile_KSP(ksp_src)
|
||||
configure_KSP(app_src)
|
||||
|
||||
os.chdir(cwd)
|
||||
|
||||
|
305
test/functional/setup/ksp_app.c
Normal file
305
test/functional/setup/ksp_app.c
Normal file
|
@ -0,0 +1,305 @@
|
|||
/******************************************************************************
|
||||
Example application for demonstration of custom keystore provider usage
|
||||
|
||||
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
|
||||
|
||||
******************************************************************************/
|
||||
|
||||
#define KSPNAME L"MyCustomKSPName"
|
||||
#define PROV_ENCRYPT_KEY "LPKCWVD07N3RG98J0MBLG4H2" /* this can be any character string */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#define __stdcall
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
#include <sql.h>
|
||||
#include <sqlext.h>
|
||||
#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;
|
||||
unsigned char foundProv = 0;
|
||||
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))) {
|
||||
foundProv = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
#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, 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'),"
|
||||
"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);
|
||||
}
|
||||
|
||||
/* 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];
|
||||
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);
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
132
test/functional/setup/myKSP.c
Normal file
132
test/functional/setup/myKSP.c
Normal file
|
@ -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 <windows.h>
|
||||
#else
|
||||
#define __stdcall
|
||||
#endif
|
||||
|
||||
#define DEBUG 0
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sqltypes.h>
|
||||
#include <sql.h>
|
||||
#include <sqlext.h>
|
||||
#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
|
||||
};
|
57
test/functional/setup/run_ksp.py
Normal file
57
test/functional/setup/run_ksp.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
#!/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'
|
||||
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
|
||||
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))
|
||||
|
||||
os.chdir(cwd)
|
25
test/functional/sqlsrv/AE_Ksp.inc
Normal file
25
test/functional/sqlsrv/AE_Ksp.inc
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
function getKSPpath()
|
||||
{
|
||||
$name = 'myKSP';
|
||||
|
||||
$dir_name = realpath(dirname(__FILE__));
|
||||
$ksp = $dir_name . DIRECTORY_SEPARATOR . $name;
|
||||
if ( strtoupper( substr( php_uname( 's' ), 0, 3 ) ) == 'WIN' ) {
|
||||
$arch = 'x64';
|
||||
if ( PHP_INT_SIZE == 4 ) // running 32 bit
|
||||
$arch = '';
|
||||
$ksp .= $arch . '.dll';
|
||||
}
|
||||
else
|
||||
$ksp .= '.so';
|
||||
|
||||
return $ksp;
|
||||
}
|
||||
|
||||
$ksp_name = 'MyCustomKSPName';
|
||||
$encrypt_key = 'LPKCWVD07N3RG98J0MBLG4H2';
|
||||
$ksp_test_table = 'CustomKSPTestTable';
|
||||
|
||||
?>
|
40
test/functional/sqlsrv/skipif_server_old.inc
Normal file
40
test/functional/sqlsrv/skipif_server_old.inc
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
if (!extension_loaded("sqlsrv"))
|
||||
die("skip extension not loaded");
|
||||
|
||||
require_once( "MsSetup.inc" );
|
||||
$connectionInfo = array( "UID"=>$userName, "PWD"=>$userPassword );
|
||||
|
||||
$conn = sqlsrv_connect( $server, $connectionInfo );
|
||||
if( ! $conn )
|
||||
{
|
||||
echo ( "Error: could not connect during SKIPIF!" );
|
||||
}
|
||||
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 )
|
||||
{
|
||||
// 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." );
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
64
test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp.phpt
Normal file
64
test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp.phpt
Normal file
|
@ -0,0 +1,64 @@
|
|||
--TEST--
|
||||
Fetch data from a prepopulated test table given a custom keystore provider
|
||||
--SKIPIF--
|
||||
<?php require('skipif_server_old.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
sqlsrv_configure( 'WarningsReturnAsErrors', 1 );
|
||||
sqlsrv_configure( 'LogSeverity', SQLSRV_LOG_SEVERITY_ALL );
|
||||
|
||||
require( 'MsSetup.inc' );
|
||||
require( 'AE_Ksp.inc' );
|
||||
|
||||
$ksp_path = getKSPpath();
|
||||
|
||||
$connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd,
|
||||
"ColumnEncryption"=>"enabled",
|
||||
"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 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] . "\tc3=" . $row[2] . "\tc4=" . $row[3] . "\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 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
|
|
@ -0,0 +1,68 @@
|
|||
--TEST--
|
||||
Fetch encrypted data from a prepopulated test table given a custom keystore provider
|
||||
--SKIPIF--
|
||||
<?php require('skipif_server_old.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
sqlsrv_configure( 'WarningsReturnAsErrors', 1 );
|
||||
sqlsrv_configure( 'LogSeverity', SQLSRV_LOG_SEVERITY_ALL );
|
||||
|
||||
require( 'MsSetup.inc' );
|
||||
require( 'AE_Ksp.inc' );
|
||||
|
||||
$ksp_path = getKSPpath();
|
||||
|
||||
$connectionInfo = array( "Database"=>$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
|
130
test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt
Normal file
130
test/functional/sqlsrv/sqlsrv_connect_encrypted_ksp_errors.phpt
Normal file
|
@ -0,0 +1,130 @@
|
|||
--TEST--
|
||||
Connect using a custom keystore provider with some required inputs missing
|
||||
--SKIPIF--
|
||||
<?php require('skipif_server_old.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function connect( $server, $connectionInfo )
|
||||
{
|
||||
$conn = sqlsrv_connect( $server, $connectionInfo );
|
||||
if( $conn === false )
|
||||
{
|
||||
echo "Failed to connect.\n";
|
||||
$errors = sqlsrv_errors();
|
||||
foreach ( $errors[0] as $key => $error )
|
||||
{
|
||||
if( is_string( $key ) )
|
||||
echo "[$key] => $error\n";
|
||||
}
|
||||
echo "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
echo "Connected successfully with ColumnEncryption enabled.\n";
|
||||
}
|
||||
|
||||
return $conn;
|
||||
}
|
||||
|
||||
sqlsrv_configure( 'LogSeverity', SQLSRV_LOG_SEVERITY_ALL );
|
||||
|
||||
require( 'MsSetup.inc' );
|
||||
require( 'AE_Ksp.inc' );
|
||||
|
||||
$ksp_path = getKSPpath();
|
||||
|
||||
echo("Connecting... with column encryption\n");
|
||||
$connectionInfo = array( "Database"=>$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.
|
||||
[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.
|
||||
[SQLSTATE] => IMSSP
|
||||
[code] => -104
|
||||
[message] => Invalid value for loading a custom keystore provider.
|
||||
|
||||
Connecting... without a name
|
||||
Failed to connect.
|
||||
[SQLSTATE] => IMSSP
|
||||
[code] => -101
|
||||
[message] => The name of the custom keystore provider is missing.
|
||||
|
||||
Connecting... with an empty name
|
||||
Failed to connect.
|
||||
[SQLSTATE] => IMSSP
|
||||
[code] => -104
|
||||
[message] => Invalid value for loading a custom keystore provider.
|
||||
|
||||
Connecting... without a key
|
||||
Failed to connect.
|
||||
[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
|
248
test/functional/sqlsrv/sqlsrv_encrypted_patients_ksp.phpt
Normal file
248
test/functional/sqlsrv/sqlsrv_encrypted_patients_ksp.phpt
Normal file
|
@ -0,0 +1,248 @@
|
|||
--TEST--
|
||||
Test simple insert, fetch and update with ColumnEncryption enabled and a custome keystore provider
|
||||
--SKIPIF--
|
||||
<?php require('skipif_server_old.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
function CreatePatientsTable()
|
||||
{
|
||||
global $conn;
|
||||
$tablename = 'Patients';
|
||||
|
||||
$stmt = sqlsrv_query( $conn, "IF OBJECT_ID('$tablename', 'U') IS NOT NULL DROP TABLE $tablename" );
|
||||
sqlsrv_free_stmt( $stmt );
|
||||
|
||||
$tsql = "CREATE TABLE $tablename (
|
||||
[PatientId] [int] IDENTITY(1,1) NOT NULL,
|
||||
[SSN] [char](11) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = Deterministic, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL,
|
||||
[FirstName] [nvarchar](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = Deterministic, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,
|
||||
[LastName] [nvarchar](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = Deterministic, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,
|
||||
[BirthDate] [date] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL)";
|
||||
|
||||
$stmt = sqlsrv_query( $conn, $tsql );
|
||||
if (! $stmt )
|
||||
{
|
||||
echo "Failed to create test table!\n";
|
||||
die( print_r( sqlsrv_errors(), true ));
|
||||
}
|
||||
|
||||
return $tablename;
|
||||
}
|
||||
|
||||
function InsertData($ssn, $fname, $lname, $date)
|
||||
{
|
||||
global $conn, $tablename;
|
||||
|
||||
$params = array(
|
||||
array($ssn, null, null, SQLSRV_SQLTYPE_CHAR(11)), array($fname, null, null, SQLSRV_SQLTYPE_NVARCHAR(50)), array($lname, null, null, SQLSRV_SQLTYPE_NVARCHAR(50)), array($date, null, null, SQLSRV_SQLTYPE_DATE)
|
||||
);
|
||||
|
||||
$tsql = "INSERT INTO $tablename (SSN, FirstName, LastName, BirthDate) VALUES (?, ?, ?, ?)";
|
||||
if (! $stmt = sqlsrv_prepare($conn, $tsql, $params))
|
||||
{
|
||||
echo "Failed to prepare statement.\n";
|
||||
print_r( sqlsrv_errors() );
|
||||
}
|
||||
|
||||
if (! sqlsrv_execute($stmt))
|
||||
{
|
||||
echo "Failed to insert a new record.\n";
|
||||
print_r( sqlsrv_errors() );
|
||||
}
|
||||
}
|
||||
|
||||
function SelectData()
|
||||
{
|
||||
global $conn, $tablename;
|
||||
|
||||
$stmt = sqlsrv_query($conn, "SELECT * FROM $tablename");
|
||||
|
||||
while ($obj = sqlsrv_fetch_object( $stmt ))
|
||||
{
|
||||
echo $obj->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
|
Loading…
Reference in a new issue