Vlastní zprostředkovatelé úložiště klíčů

Stáhnout ovladač ODBC

Přehled

Funkce šifrování sloupců SQL Serveru 2016 vyžaduje, aby klient načítal šifrovací klíče šifrovaných sloupců (ECEKs) uložené na serveru a potom se dešifroval do šifrovacích klíčů sloupců (CEKs), aby bylo možné získat přístup k datům uloženým v šifrovaných sloupcích. Sady ECEKs jsou šifrované hlavními klíči sloupců (CMK) a zabezpečení klíče CMK je důležité pro zabezpečení šifrování sloupců. CmK by tedy měl být uložen v zabezpečeném umístění; Účelem zprostředkovatele úložiště klíčů šifrování sloupce je poskytnout rozhraní, které ovladači ODBC umožní přístup k těmto bezpečně uloženým klíčům CMK. Pro uživatele s vlastním zabezpečeným úložištěm poskytuje rozhraní zprostředkovatele vlastního úložiště klíčů architekturu pro implementaci přístupu k zabezpečenému úložišti CMK pro ovladač ODBC, který se pak dá použít k provádění šifrování a dešifrování CEK.

Každý zprostředkovatel úložiště klíčů obsahuje a spravuje jednu nebo více sad CMK, které jsou identifikovány cestami klíčů – řetězce formátu definovaného poskytovatelem. Tento klíč CMK spolu s šifrovacím algoritmem, také řetězec definovaný zprostředkovatelem, lze použít k provedení šifrování klíče CEK a dešifrování ECEK. Algoritmus spolu s ECEK a názvem poskytovatele se ukládají v metadatech šifrování databáze. Pro více informací se podívejte na CREATE COLUMN MASTER KEY a CREATE COLUMN ENCRYPTION KEY. Dva základní operace správy klíčů jsou tedy:

CEK = DecryptViaCEKeystoreProvider(CEKeystoreProvider_name, Key_path, Key_algorithm, ECEK)

-and-

ECEK = EncryptViaCEKeystoreProvider(CEKeyStoreProvider_name, Key_path, Key_algorithm, CEK)

CEKeystoreProvider_name je použit k identifikaci konkrétního zprostředkovatele úložiště klíčů pro šifrování sloupců (CEKeystoreProvider), a ostatní argumenty slouží pro šifrování/dešifrování klíče (E)CEK pomocí tohoto zprostředkovatele. Název a cesta ke klíči jsou poskytovány metadaty CMK, zatímco algoritmus a hodnota ECEK jsou poskytovány metadaty CEK. Vedle výchozích předdefinovaných zprostředkovatelů může existovat několik poskytovatelů úložiště klíčů. Při provádění operace, která vyžaduje klíč CEK, ovladač použije metadata CMK k vyhledání příslušného poskytovatele úložiště klíčů podle názvu a provede jeho dešifrovací operaci, která se dá vyjádřit takto:

CEK = CEKeyStoreProvider_specific_decrypt(Key_path, Key_algorithm, ECEK)

Ačkoli ovladač nemusí šifrovat šifrovací klíče CEK, nástroj pro správu klíčů to může potřebovat k implementaci operací, jako je vytváření a obměna hlavních šifrovacích klíčů CMK. Tyto akce vyžadují provedení inverzní operace:

ECEK = CEKeyStoreProvider_specific_encrypt(Key_path, Key_algorithm, CEK)

Rozhraní CEKeyStoreProvider

Tento dokument podrobně popisuje rozhraní CEKeyStoreProvider. Zprostředkovatel úložiště klíčů, který implementuje toto rozhraní, může použít ovladač Microsoft ODBC pro SQL Server. Implementátory CEKeyStoreProvider mohou pomocí tohoto průvodce vyvíjet vlastní zprostředkovatele úložiště klíčů použitelné ovladačem.

Knihovna zprostředkovatele úložiště klíčů ("knihovna zprostředkovatele") je dynamická knihovna, kterou může načíst ovladač ODBC a obsahuje jednoho nebo více zprostředkovatelů úložiště klíčů. Symbol CEKeystoreProvider musí být exportován knihovnou zprostředkovatele a musí být adresou pole ukazatelů na CEKeystoreProvider struktury ukončené hodnotou null, jedno pro každého zprostředkovatele úložiště klíčů v knihovně.

Struktura CEKeystoreProvider definuje vstupní body jednoho zprostředkovatele úložiště klíčů:

typedef struct CEKeystoreProvider {
    wchar_t *Name;
    int (*Init)(CEKEYSTORECONTEXT *ctx, errFunc *onError);
    int (*Read)(CEKEYSTORECONTEXT *ctx, errFunc *onError, void *data, unsigned int *len);
    int (*Write)(CEKEYSTORECONTEXT *ctx, errFunc *onError, void *data, unsigned int len);
    int (*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 (*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 (*Free)();
} CEKEYSTOREPROVIDER;
Název pole Description
Name Název zprostředkovatele úložiště klíčů. Nesmí být stejný jako žádný jiný poskytovatel keystore, který dříve načetl ovladač nebo je obsažen v této knihovně. Řetězec s nulovým ukončením, širokým znakem*
Init Inicializační funkce. Pokud není vyžadována inicializační funkce, může být toto pole null.
Read Funkce pro čtení dat poskytnutých zprostředkovatelem Může mít hodnotu null, pokud není vyžadována.
Write Funkce pro zápis poskytovatele Vyžaduje se, pokud čtení nemá hodnotu null. Může mít hodnotu null, pokud není vyžadována.
DecryptCEK Funkce dešifrování ECEK Tato funkce je důvodem existence zprostředkovatele úložiště klíčů a nesmí být null.
EncryptCEK Šifrovací funkce CEK. Ovladač tuto funkci nevolá, ale je poskytována pro umožnění programového přístupu k vytváření ECEK pomocí nástrojů pro správu klíčů. Může mít hodnotu null, pokud není vyžadována.
Free Funkce ukončení. Může mít hodnotu null, pokud není vyžadována.

S výjimkou Free mají funkce v tomto rozhraní všechny dvojice parametrů, ctx a onError. První označuje kontext, ve kterém je funkce volána, zatímco druhá se používá k hlášení chyb. Další informace najdete v tématu Kontexty a zpracování chyb níže.

int Init(CEKEYSTORECONTEXT *ctx, errFunc onError);

Zástupný název pro inicializační funkci definovanou zprostředkovatelem Ovladač tuto funkci volá jednou po načtení poskytovatele, ale před prvním použitím, kdy ji potřebuje pro provedení dešifrování ECEK nebo zpracování požadavků pro čtení/zápis. Tuto funkci použijte k provedení jakékoli inicializace, kterou potřebuje.

Argument Description
ctx [Vstup] Kontext operace.
onError [Vstup] Funkce zasílání zpráv o chybách
Return Value Vrácení nenulové hodnoty označující úspěch nebo nulu pro označení selhání.
int Read(CEKEYSTORECONTEXT *ctx, errFunc onError, void *data, unsigned int *len);

Zástupný název pro komunikační funkci definovanou poskytovatelem Ovladač volá tuto funkci, když aplikace požaduje čtení dat od poskytovatele, který byl dříve zapsán, pomocí atributu připojení SQL_COPT_SS_CEKEYSTOREDATA, což umožňuje aplikaci číst libovolná data od poskytovatele. Další informace naleznete v tématu Komunikace s zprostředkovateli úložiště klíčů.

Argument Description
ctx [Vstup] Kontext operace.
onError [Vstup] Funkce zasílání zpráv o chybách
data [Výstup] Ukazatel na vyrovnávací paměť, ve které zprostředkovatel zapisuje data, která má aplikace číst. Tato vyrovnávací paměť odpovídá datovému poli struktury CEKEYSTOREDATA.
len [InOut] Ukazatel na hodnotu délky; při vstupu je tato hodnota maximální délkou vyrovnávací paměti dat a poskytovatel do ní nesmí zapisovat více než *len bajty. Po vrácení by měl poskytovatel aktualizovat *len s počtem zapsaných bajtů.
Return Value Vrácení nenulové hodnoty označující úspěch nebo nulu pro označení selhání.
int Write(CEKEYSTORECONTEXT *ctx, errFunc onError, void *data, unsigned int len);

Zástupný název pro komunikační funkci definovanou poskytovatelem Ovladač volá tuto funkci, když aplikace požaduje zápis dat do zprostředkovatele pomocí atributu připojení SQL_COPT_SS_CEKEYSTOREDATA, což aplikaci umožňuje zapisovat do zprostředkovatele libovolná data. Další informace naleznete v tématu Komunikace s zprostředkovateli úložiště klíčů.

Argument Description
ctx [Vstup] Kontext operace.
onError [Vstup] Funkce zasílání zpráv o chybách
data [Vstup] Ukazatel na vyrovnávací paměť obsahující data, která má poskytovatel číst. Tato vyrovnávací paměť odpovídá datovému poli struktury CEKEYSTOREDATA. Zprostředkovatel nesmí číst více bajtů, než je hodnota len, z této vyrovnávací paměti.
len [Vstup] Počet bajtů dostupných v datech. Tato hodnota odpovídá poli dataSize struktury CEKEYSTOREDATA.
Return Value Vrácení nenulové hodnoty označující úspěch nebo nulu pro označení selhání.
int (*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);

Zástupný název pro dešifrovací funkci ECEK definovanou poskytovatelem Ovladač volá tuto funkci k dešifrování ECEK šifrovaného klíčem CMK přidruženým k tomuto poskytovateli do CEK.

Argument Description
ctx [Vstup] Kontext operace.
onError [Vstup] Funkce zasílání zpráv o chybách
keyPath [Vstup] Hodnota atributu metadat KEY_PATH pro CMK, na který odkazuje daný ECEK. Řetězec s širokým znakem* ukončený hodnotou null. Tato hodnota je určena k identifikaci klíče CMK zpracovávané tímto poskytovatelem.
alg [Vstup] Hodnota atributu metadat ALGORITMU pro danou ECEK. Řetězec s širokým znakem* ukončený hodnotou null. Tato hodnota je určená k identifikaci šifrovacího algoritmu použitého k šifrování daného ECEK.
ecek [Vstup] Ukazatel na ECEK, který se má dešifrovat.
ecekLen [Vstup] Délka ECEK.
cekOut [Výstup] Poskytovatel přidělí paměť pro dešifrovaný ECEK a zapíše jeho adresu na ukazatel, na který odkazuje cekOut. Tento blok paměti musí být možné uvolnit pomocí funkce LocalFree (Windows) nebo free (Linux/macOS). Pokud nebyla kvůli chybě přidělena žádná paměť nebo jinak, poskytovatel nastaví *cekOut na nulový ukazatel.
cekLen [Výstup] Poskytovatel zapíše na adresu, na kterou odkazuje cekLen délku dešifrovaného ECEK, kterou napsal do **cekOut.
Return Value Vrácení nenulové hodnoty označující úspěch nebo nulu pro označení selhání.
int (*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);

Zástupný název pro šifrovací funkci CEK definovanou zprostředkovatelem Ovladač tuto funkci nevolá ani nezpřístupňuje její funkce prostřednictvím rozhraní ODBC, ale je k dispozici pro programový přístup k vytváření ECEK nástroji pro správu klíčů.

Argument Description
ctx [Vstup] Kontext operace.
onError [Vstup] Funkce zasílání zpráv o chybách
keyPath [Vstup] Hodnota atributu metadat KEY_PATH pro CMK, na který odkazuje daný ECEK. Řetězec s širokým znakem* ukončený hodnotou null. Tato hodnota je určena k identifikaci klíče CMK zpracovávané tímto poskytovatelem.
alg [Vstup] Hodnota atributu metadat ALGORITMU pro danou ECEK. Řetězec s širokým znakem* ukončený hodnotou null. Tato hodnota je určená k identifikaci šifrovacího algoritmu použitého k šifrování daného ECEK.
cek [Vstup] Ukazatel na klíč CEK, který se má šifrovat.
cekLen [Vstup] Délka CEK.
ecekOut [Výstup] Poskytovatel přidělí paměť pro šifrovaný klíč CEK a zapíše jeho adresu na ukazatel, na který odkazuje ecekOut. Tento blok paměti musí být možné uvolnit pomocí funkce LocalFree (Windows) nebo free (Linux/macOS). Pokud nebyla kvůli chybě přidělena žádná paměť nebo jinak, poskytovatel nastaví *ecekOut na nulový ukazatel.
ecekLen [Výstup] Poskytovatel zapíše na adresu, na kterou odkazuje ecekLen, délku šifrovaného CEK, kterou zapsal do **ecekOut.
Return Value Vrácení nenulové hodnoty označující úspěch nebo nulu pro označení selhání.
void (*Free)();

Zástupný název pro funkci ukončení definovanou poskytovatelem Ovladač může tuto funkci volat při normálním ukončení procesu.

Poznámka:

Řetězce širokého znaku jsou 2 bajtové znaky (UTF-16), protože sql Server je ukládá.

Zpracování chyb

Vzhledem k tomu, že během zpracování poskytovatele může dojít k chybám, je k dispozici mechanismus, který mu umožní hlásit chyby zpět ovladači konkrétněji než logická úspěšnost nebo selhání. Řada funkcí má několik parametrů, ctx a onError, které se společně používají pro tento účel, a navíc k návratové hodnotě úspěchu/selhání.

Parametr ctx identifikuje kontext, ve kterém dojde k operaci zprostředkovatele.

Parametr onError odkazuje na funkci zasílání zpráv o chybách s následujícím prototypem:

typedef void errFunc(CEKEYSTORECONTEXT *ctx, const wchar_t *msg, ...);

Argument Description
ctx [Vstup] Kontext pro nahlášení chyby.
msg [Vstup] Chybová zpráva, kterou je potřeba hlásit. Širokoznakový řetězec ukončený nulovým znakem. Chcete-li povolit parametrizované informace, může tento řetězec obsahovat sekvence formátování vložení formuláře přijaté funkcí FormatMessage . Rozšířenou funkčnost může specifikovat tento parametr, jak je popsáno níže.
... [Vstup] Zvláštní variadické parametry pro přizpůsobení specifikátorů formátu v msg podle potřeby.

Pokud chcete hlásit, kdy došlo k chybě, poskytovatel volá funkci onError, poskytujíc parametr kontextu předaný do funkce provider ovladačem a chybovou zprávu s volitelnými dodatečnými parametry, které mají být naformátovány v ní. Zprostředkovatel může tuto funkci volat vícekrát, aby v rámci vyvolání jedné funkce zprostředkovatele po sobě posílala více chybových zpráv. Například:

    if (!doSomething(...))
    {
        onError(ctx, L"An error occurred in doSomething.");
        onError(ctx, L"Additional error message with more details.");
        return 0;
    }

Parametr msg je obvykle řetězec širokého znaku, ale k dispozici jsou další rozšíření:

Pomocí jedné ze speciálních předdefinovaných hodnot s makrem IDS_MSG mohou být použity obecné chybové zprávy již existující a v lokalizované podobě v ovladači. Pokud například poskytovatel nepřidělí paměť, IDS_S1_001 může se použít zpráva "Selhání přidělení paměti":

onError(ctx, IDS_MSG(IDS_S1_001));

Aby ovladač chybu rozpoznal, musí funkce zprostředkovatele vrátit chybu. Pokud dojde k selhání v kontextu operace ODBC, vystavené chyby budou přístupné na připojení nebo popisovač příkazu prostřednictvím standardního diagnostického mechanismu ODBC (SQLError, SQLGetDiagReca SQLGetDiagField).

Přidružení kontextu

Strukturu CEKEYSTORECONTEXT lze kromě poskytnutí kontextu zpětného volání chyby použít také k určení kontextu ODBC, ve kterém se provádí operace zprostředkovatele. Tento kontext umožňuje poskytovateli přidružit data ke každému z těchto kontextů, například implementovat konfiguraci jednotlivých připojení. Pro tento účel obsahuje struktura tři neprůhlené ukazatele odpovídající kontextu prostředí, připojení a příkazu:

typedef struct CEKeystoreContext
{
void *envCtx;
void *dbcCtx;
void *stmtCtx;
} CEKEYSTORECONTEXT;
Obor Description
envCtx Kontext prostředí
dbcCtx Kontext připojení.
stmtCtx Kontext příkazu.

Každý z těchto kontextů je neprůhledná hodnota, která není stejná jako odpovídající popisovač ODBC. Pokud je popisovač X přidružen k kontextové hodnotě Y, pak žádné jiné prostředí, připojení ani popisovač příkazu, které existují současně s X, nebudou mít kontextovou hodnotu Y a k popisovači X nebudou přidruženy žádné další kontextové hodnoty. Pokud operace zprostředkovatele nemá konkrétní kontext popisovače (například volání SQLSetConnectAttr pro načtení a konfiguraci zprostředkovatelů, ve kterých neexistuje žádný popisovač příkazu), odpovídající kontextová hodnota ve struktuře má hodnotu null.

Example

Zprostředkovatel úložiště klíčů

Následující kód je příkladem minimální implementace zprostředkovatele úložiště klíčů.

/* Custom Keystore Provider Example

Windows:   compile with cl MyKSP.c /LD /MD /link /out:MyKSP.dll
Linux/macOS: compile with gcc -fshort-wchar -fPIC -o MyKSP.so -shared MyKSP.c

 */

#ifdef _WIN32
#include <windows.h>
#else
#define __stdcall
#endif

#include <stdlib.h>
#include <sqltypes.h>
#include "msodbcsql.h"
#include <sql.h>
#include <sqlext.h>

int __stdcall KeystoreInit(CEKEYSTORECONTEXT *ctx, errFunc *onError) {
    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) {
    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;
    printf("KSP Decrypt() function called (keypath=%S alg=%S ecekLen=%u)\n", keyPath, alg, ecekLen);
    if (wcscmp(keyPath, L"TheOneAndOnlyKey")) {
        onError(ctx, L"Invalid key path");
        return 0;
    }
    if (wcscmp(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;
    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
};

Aplikace ODBC

Následující kód je ukázková aplikace, která používá zprostředkovatele úložiště klíčů výše. Při spuštění se ujistěte, že je knihovna zprostředkovatele ve stejném adresáři jako binární soubor aplikace a že připojovací řetězec určuje (nebo určuje název DSN, který obsahuje) ColumnEncryption=Enabled nastavení.

/*
 Example application for demonstration of custom keystore provider usage

Windows:   compile with cl /MD kspapp.c /link odbc32.lib
Linux/macOS: compile with gcc -o kspapp -fshort-wchar kspapp.c -lodbc -ldl
 
 usage: kspapp connstr

 */

#define KSPNAME L"MyCustomKSPName"
#define PROV_ENCRYPT_KEY "JHKCWYT06N3RG98J0MBLG4E3"

#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"

/* 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 main(int argc, char **argv) {
    char sqlbuf[1024];
    SQLHENV env;
    SQLHDBC dbc;
    SQLHSTMT stmt;
    SQLRETURN rc;
    unsigned char CEK[32];
    unsigned char *ECEK;
    unsigned short ECEKlen;
#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 *);
    int i;
    if (argc < 2) {
        fprintf(stderr, "usage: kspapp connstr\n");
        return 1;
    }

    /* 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;
    }

    /* 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, argv[1], strlen(argv[1]), 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);

    /* 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

    /* 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 the [CustomKSPTestTable]. You may inspect the data now.\n"
            "Press Enter to continue...\n");
        getchar();
    }

    /* Retrieve the data */
    {
        int c1;
        char c2[256];
        rc = SQLBindCol(stmt, 1, SQL_C_LONG, &c1, 0, 0);
        checkRC(rc, "Binding columns for select", 11, stmt, SQL_HANDLE_STMT);
        rc = SQLBindCol(stmt, 2, SQL_C_CHAR, c2, sizeof(c2), 0);
        checkRC(rc, "Binding columns for select", 11, stmt, SQL_HANDLE_STMT);
        rc = SQLExecDirect(stmt, "SELECT c1, c2 FROM CustomKSPTestTable", SQL_NTS);
        checkRC(rc, "Retrieving rows query", 12, stmt, SQL_HANDLE_STMT);
        while (SQL_SUCCESS == (rc = SQLFetch(stmt)))
            printf("Retrieved data: c1=%d c2=%s\n", c1, c2);
        SQLFreeStmt(stmt, SQL_CLOSE);
        printf("Press Enter to clean up and exit...\n");
        getchar();
    }

    /* Clean up */
    {
        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;
}