Udostępnij przez


Niestandardowi dostawcy magazynu kluczy

pobierz sterownika ODBC

Przegląd

Funkcja szyfrowania kolumn programu SQL Server 2016 wymaga, aby zaszyfrowane klucze szyfrowania kolumn (ECEK) przechowywane na serwerze zostały pobrane przez klienta, a następnie odszyfrowane do kluczy szyfrowania kolumn w celu uzyskania dostępu do danych przechowywanych w zaszyfrowanych kolumnach. Klucze ECEK są szyfrowane przez Klucze Główne Kolumn (CMK), a bezpieczeństwo CMK jest kluczowe dla zabezpieczenia szyfrowania kolumn. Dlatego klucz CMK powinien być przechowywany w bezpiecznej lokalizacji; celem dostawcy magazynu kluczy szyfrowania kolumn jest zapewnienie interfejsu umożliwiającego sterownikowi ODBC uzyskiwanie dostępu do tych bezpiecznie przechowywanych CMK. W przypadku użytkowników z własnym bezpiecznym magazynem interfejs niestandardowego dostawcy magazynu kluczy udostępnia platformę do implementowania dostępu do bezpiecznego magazynu klucza zarządzanego dla sterownika ODBC, który może następnie służyć do szyfrowania i odszyfrowywania klucza CEK.

Każdy dostawca magazynu kluczy zawiera i zarządza co najmniej jednym lub większą liczbą CMK, które są identyfikowane przez ścieżki kluczy: ciągi w formacie zdefiniowanym przez dostawcę. Ten klucz CMK, wraz z algorytmem szyfrowania, oraz ciąg zdefiniowany przez dostawcę, może być użyty do zaszyfrowania klucza CEK i odszyfrowania klucza ECEK. Algorytm wraz z kluczem ECEK i nazwą dostawcy są przechowywane w metadanych szyfrowania bazy danych. Aby uzyskać więcej informacji, zobacz CREATE COLUMN MASTER KEY (TWORZENIE KLUCZA GŁÓWNEGO KOLUMNY) i CREATE COLUMN ENCRYPTION KEY (TWORZENIE KLUCZA SZYFROWANIA KOLUMN). W związku z tym dwa podstawowe operacje zarządzania kluczami to:

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

-and-

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

`CEKeystoreProvider_name jest używany do identyfikacji określonego dostawcy magazynu kluczy szyfrowania kolumn (CEKeystoreProvider), a pozostałe argumenty są wykorzystywane do szyfrowania/odszyfrowywania (E)CEK.` Nazwa i ścieżka klucza są dostarczane przez metadane CMK, a algorytm i wartość ECEK są dostarczane przez metadane CEK. Więcej niż jeden dostawca magazynu kluczy może być obecny razem z domyślnymi zintegrowanymi dostawcami. Po wykonaniu operacji, która wymaga klucza CEK, sterownik używa metadanych CMK do znalezienia odpowiedniego dostawcy magazynu kluczy według nazwy i wykonuje operację odszyfrowania, którą można wyrazić jako:

CEK = CEKeyStoreProvider_specific_decrypt(Key_path, Key_algorithm, ECEK)

Chociaż sterownik nie musi szyfrować CEK, narzędzie do zarządzania kluczami może to zrobić, aby zaimplementować operacje, takie jak tworzenie i rotacja klucza głównego (CMK). Te akcje wymagają wykonania operacji odwrotnej:

ECEK = CEKeyStoreProvider_specific_encrypt(Key_path, Key_algorithm, CEK)

interfejs CEKeyStoreProvider

W tym dokumencie opisano szczegółowo interfejs CEKeyStoreProvider. Dostawca magazynu kluczy, implementujący ten interfejs, może być używany przez sterownik Microsoft ODBC dla programu SQL Server. Implementatory CEKeyStoreProvider mogą użyć tego przewodnika, aby opracować niestandardowych dostawców magazynu kluczy, których można używać przez sterownik.

Biblioteka dostawcy magazynu kluczy ("biblioteka dostawcy") to biblioteka dynamiczna, która może zostać załadowana przez sterownik ODBC i zawiera co najmniej jednego dostawcę magazynu kluczy. Symbol CEKeystoreProvider musi być eksportowany przez bibliotekę dostawcy i być adresem tablicy wskaźników zakończonej wartością null do struktur CEKeystoreProvider, po jednej dla każdego dostawcy magazynu kluczy w tej bibliotece.

Struktura CEKeystoreProvider definiuje punkty wejścia pojedynczego dostawcy sklepu kluczy.

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;
Nazwa pola Description
Name Nazwa dostawcy magazynu kluczy. Nie może być taki sam jak żaden inny dostawca magazynu kluczy, który był wcześniej załadowany przez sterownik lub jest obecny w tej bibliotece. Ciag znaków szerokoznakowych zakończony wartością null.
Init Funkcja inicjowania. Jeśli funkcja inicjowania nie jest wymagana, to pole może mieć wartość null.
Read Funkcja odczytu dostawcy. Może mieć wartość null, jeśli nie jest to wymagane.
Write Funkcja zapisu dostawcy. Wymagane, jeśli odczyt nie ma wartości null. Może mieć wartość null, jeśli nie jest to wymagane.
DecryptCEK Funkcja odszyfrowywania ECEK. Ta funkcja jest przyczyną istnienia dostawcy magazynu kluczy i nie może mieć wartości null.
EncryptCEK Funkcja szyfrowania CEK. Chociaż sterownik jej nie wywołuje, funkcja ta jest dostarczana, aby umożliwić programowy dostęp do tworzenia ECEK przez narzędzia do zarządzania kluczami. Może mieć wartość null, jeśli nie jest to wymagane.
Free Funkcja kończenia. Może mieć wartość null, jeśli nie jest to wymagane.

Z wyjątkiem funkcji Free, funkcje w tym interfejsie mają parę parametrów, ctx i onError. Pierwszy z nich identyfikuje kontekst, w którym wywoływana jest funkcja, podczas gdy drugi jest używany do raportowania błędów. Aby uzyskać więcej informacji, zobacz Konteksty i obsługa błędów poniżej.

int Init(CEKEYSTORECONTEXT *ctx, errFunc onError);

Nazwa symbolu zastępczego funkcji inicjowania zdefiniowanej przez dostawcę. Sterownik wywołuje tę funkcję raz po załadowaniu dostawcy, ale przed pierwszym użyciem go do wykonania żądań odszyfrowywania ECEK lub Read()/Write(). Użyj tej funkcji, aby wykonać dowolną inicjację, jakiej potrzebuje.

Argument Description
ctx [Dane wejściowe] Kontekst operacji.
onError [Dane wejściowe] Funkcja raportowania błędów.
Return Value Zwróć wartość niezerową, aby wskazać powodzenie lub zero, aby wskazać niepowodzenie.
int Read(CEKEYSTORECONTEXT *ctx, errFunc onError, void *data, unsigned int *len);

Nazwa zastępcza dla funkcji komunikacyjnej zdefiniowanej przez dostawcę. Sterownik wywołuje tę funkcję, gdy aplikacja żąda odczytu danych od dostawcy (wcześniej zapisanego) przy użyciu atrybutu połączenia SQL_COPT_SS_CEKEYSTOREDATA, co umożliwia aplikacji odczytywanie dowolnych danych od dostawcy. Aby uzyskać więcej informacji, zobacz Komunikacja z dostawcami Keystore.

Argument Description
ctx [Dane wejściowe] Kontekst operacji.
onError [Dane wejściowe] Funkcja raportowania błędów.
data [Dane wyjściowe] Wskaźnik do buforu, w którym dostawca zapisuje dane do odczytu przez aplikację. Ten bufor odpowiada polu danych struktury CEKEYSTOREDATA.
len [InOut] Wskaźnik do wartości długości; po wejściu ta wartość jest maksymalną długością buforu danych, a dostawca nie zapisuje do niego więcej niż *len bajtów. Po powrocie dostawca powinien zaktualizować *len o liczbę zapisanych bajtów.
Return Value Zwróć wartość niezerową, aby wskazać powodzenie lub zero, aby wskazać niepowodzenie.
int Write(CEKEYSTORECONTEXT *ctx, errFunc onError, void *data, unsigned int len);

Nazwa zastępcza dla funkcji komunikacyjnej zdefiniowanej przez dostawcę. Sterownik wywołuje tę funkcję, gdy aplikacja żąda zapisu danych do dostawcy przy użyciu atrybutu połączenia SQL_COPT_SS_CEKEYSTOREDATA, co umożliwia aplikacji zapisywanie dowolnych danych u dostawcy. Aby uzyskać więcej informacji, zobacz Komunikacja z dostawcami magazynu kluczy.

Argument Description
ctx [Dane wejściowe] Kontekst operacji.
onError [Dane wejściowe] Funkcja raportowania błędów.
data [Dane wejściowe] Wskaźnik do buforu zawierającego dane, które mają być odczytywane przez dostawcę. Ten bufor odpowiada polu danych struktury CEKEYSTOREDATA. Dostawca nie może odczytywać więcej niż len bajtów z tego buforu.
len [Dane wejściowe] Liczba bajtów dostępnych w danych. Ta wartość odpowiada polu dataSize struktury CEKEYSTOREDATA.
Return Value Zwróć wartość niezerową, aby wskazać powodzenie lub zero, aby wskazać niepowodzenie.
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);

Nazwa symbolu zastępczego dla funkcji odszyfrowywania ECEK zdefiniowanej przez dostawcę. Sterownik wywołuje tę funkcję, aby odszyfrować klucz ECEK, który został zaszyfrowany przy użyciu CMK związanego z tym dostawcą, do postaci CEK.

Argument Description
ctx [Dane wejściowe] Kontekst operacji.
onError [Dane wejściowe] Funkcja raportowania błędów.
keyPath [Dane wejściowe] Wartość atrybutu metadanych KEY_PATH dla CMK przywoływanego przez dany ECEK. Łańcuch szerokoznakowy* zakończony wartością null. Ta wartość ma na celu zidentyfikowanie CMK (klucza głównego klienta) obsługiwanego przez tego dostawcę.
alg [Dane wejściowe] Wartość atrybutu metadanych ALGORITHM dla podanego ECEK. Ciąg szerokoznakowy* zakończony znakiem null. Ta wartość ma na celu zidentyfikowanie algorytmu szyfrowania używanego do szyfrowania danego klucza ECEK.
ecek [Dane wejściowe] Wskaźnik na klucz ECEK do odszyfrowania.
ecekLen [Dane wejściowe] Długość ECEK.
cekOut [Dane wyjściowe] Dostawca przydziela pamięć dla odszyfrowanego klucza ECEK i zapisuje jego adres do wskaźnika wskazywanego przez cekOut. Należy zwolnić ten blok pamięci przy użyciu funkcji LocalFree (Windows) lub bezpłatnej (Linux/macOS). Jeśli pamięć nie została przydzielona z powodu błędu lub w inny sposób, dostawca ustawi *cekOut na wskaźnik o wartości null.
cekLen [Dane wyjściowe] Dostawca zapisuje pod adresem wskazanym przez cekLen długość odszyfrowanego ECEK, którą zapisał do **cekOut.
Return Value Zwróć wartość niezerową, aby wskazać powodzenie lub zero, aby wskazać niepowodzenie.
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);

Nazwa symbolu zastępczego dla funkcji szyfrowania CEK zdefiniowanej przez dostawcę. Sterownik nie wywołuje tej funkcji ani nie uwidacznia jej funkcjonalności za pośrednictwem interfejsu ODBC, ale zapewnia dostęp programowy do tworzenia ecEK przez narzędzia do zarządzania kluczami.

Argument Description
ctx [Dane wejściowe] Kontekst operacji.
onError [Dane wejściowe] Funkcja raportowania błędów.
keyPath [Dane wejściowe] Wartość atrybutu metadanych KEY_PATH dla atrybutu CMK, do którego odnosi się dany ECEK. Ciąg znaków szerokich zakończony znakiem null. Ta wartość ma na celu zidentyfikowanie klucza głównego klienta (CMK) zarządzanego przez tego dostawcę.
alg [Dane wejściowe] Wartość atrybutu metadanych ALGORITHM dla danego ECEK. Ciąg znaków szerokiego formatu zakończony wartością null*. Ta wartość ma na celu zidentyfikowanie algorytmu szyfrowania używanego do szyfrowania danego klucza ECEK.
cek [Dane wejściowe] Wskaźnik do klucza CEK do zaszyfrowania.
cekLen [Dane wejściowe] Długość klucza CEK.
ecekOut [Dane wyjściowe] Dostawca przydziela pamięć zaszyfrowanego klucza certyfikacji i zapisuje swój adres do wskaźnika wskazywanego przez ecekOut. Należy zwolnić ten blok pamięci przy użyciu funkcji LocalFree (Windows) lub bezpłatnej (Linux/macOS). Jeśli pamięć nie została przydzielona z powodu błędu lub w inny sposób, dostawca ustawi *ecekOut na wskaźnik o wartości null.
ecekLen [Dane wyjściowe] Dostawca zapisuje na adres wskazywany przez ecekLen długość zaszyfrowanego klucza CEK, który zapisał do **ecekOut.
Return Value Zwróć wartość niezerową, aby wskazać powodzenie lub zero, aby wskazać niepowodzenie.
void (*Free)();

Nazwa zastępcza dla funkcji zakończenia zdefiniowanej przez dostawcę. Sterownik może wywołać tę funkcję po normalnym zakończeniu procesu.

Uwaga / Notatka

Ciągi wieloznaczne to 2-bajtowe znaki (UTF-16) ze względu na sposób przechowywania ich przez program SQL Server.

Obsługa błędów

Ponieważ podczas przetwarzania dostawcy mogą wystąpić błędy, zapewnia się mechanizm umożliwiający zgłaszanie błędów z powrotem do sterownika w bardziej szczegółowy sposób niż powodzenie/niepowodzenie logiczne. Wiele funkcji ma parę parametrów, ctx i onError, które są używane razem do tego celu oprócz wartości zwracanej powodzenia/niepowodzenia.

Parametr ctx identyfikuje kontekst, w którym występuje operacja dostawcy.

Parametr onError wskazuje funkcję raportowania błędów z następującym prototypem:

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

Argument Description
ctx [Dane wejściowe] Kontekst do zgłaszania błędu.
msg [Dane wejściowe] Komunikat o błędzie do zgłoszenia. Ciąg szerokoznakowy zakończony wartością null. Aby zezwolić na sparametryzowane informacje, ten ciąg może zawierać sekwencje formatowania wstawiania formularza akceptowane przez funkcję FormatMessage . Rozszerzone funkcje mogą być określone przez ten parametr, jak opisano poniżej.
...\ [Dane wejściowe] Dodatkowe parametry wariadyczne do dopasowania specyfikatorów formatu w msg, zgodnie z potrzebami.

Aby zgłosić, kiedy wystąpił błąd, dostawca wywołuje metodę onError, podając parametr kontekstu przekazany do funkcji dostawcy przez sterownik i komunikat o błędzie z opcjonalnymi dodatkowymi parametrami, które mają być w nim sformatowane. Dostawca może wywołać tę funkcję wiele razy, aby po raz kolejny opublikować wiele komunikatów o błędach w ramach jednego wywołania funkcji dostawcy. Przykład:

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

Parametr msg jest zwykle ciągiem o szerokim znaku, ale dostępnych jest więcej rozszerzeń:

Korzystając z jednej ze specjalnych wstępnie zdefiniowanych wartości z makrem IDS_MSG, ogólne komunikaty o błędach już istniejące i w zlokalizowanej postaci w sterowniku mogą być używane. Jeśli na przykład dostawca nie może przydzielić pamięci, można użyć komunikatu IDS_S1_001 "Błąd alokacji pamięci":

onError(ctx, IDS_MSG(IDS_S1_001));

Aby błąd został rozpoznany przez sterownik, funkcja dostawcy musi zwrócić informację o niepowodzeniu. W przypadku awarii w kontekście operacji ODBC opublikowane błędy staną się dostępne na dojściu połączenia lub instrukcji za pośrednictwem standardowego mechanizmu diagnostyki ODBC (SQLError, SQLGetDiagReci SQLGetDiagField).

Skojarzenie kontekstu

Struktura CEKEYSTORECONTEXT, oprócz zapewnienia kontekstu wywołania zwrotnego w przypadku błędów, może również służyć do określania kontekstu ODBC, w którym wykonywana jest operacja przez dostawcę. Ten kontekst umożliwia dostawcy kojarzenie danych z każdym z tych kontekstów, na przykład w celu zaimplementowania konfiguracji poszczególnych połączeń. W tym celu struktura zawiera trzy nieprzezroczyste wskaźniki odpowiadające kontekstowi środowiska, połączenia i instrukcji:

typedef struct CEKeystoreContext
{
void *envCtx;
void *dbcCtx;
void *stmtCtx;
} CEKEYSTORECONTEXT;
(No changes needed) Description
envCtx Kontekst środowiska.
dbcCtx Kontekst połączenia.
stmtCtx Kontekst oświadczenia.

Każdy z tych kontekstów jest nieprzezroczystą wartością, która, choć nie jest taka sama jak odpowiedni uchwyt ODBC, może być używana jako unikatowy identyfikator uchwytu. Jeśli uchwyt X jest skojarzony z wartością kontekstu Y, to żadne inne środowisko, połączenie ani instrukcja, które istnieją jednocześnie z X, nie będą miały wartości kontekstu Y, ani żadne inne wartości kontekstu nie będą skojarzone z uchwytem X. Jeśli operacja dostawcy nie ma określonego kontekstu uchwytu (na przykład wywołania SQLSetConnectAttr do ładowania i konfigurowania dostawców, gdzie nie istnieje uchwyt instrukcji), odpowiednia wartość kontekstu w strukturze będzie miała wartość null.

Example

Dostawca magazynu kluczy

Poniższy kod jest przykładem minimalnej implementacji dostawcy repozytorium kluczy.

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

Aplikacja ODBC

Poniższy kod to aplikacja demonstracyjna korzystająca z powyższego dostawcy magazynu kluczy. Podczas uruchamiania upewnij się, że biblioteka dostawcy znajduje się w tym samym katalogu co plik binarny aplikacji i czy parametry połączenia określają (lub określa nazwę DSN zawierającą ColumnEncryption=Enabled ) ustawienie.

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

Zobacz też

Używanie funkcji Always Encrypted ze sterownikiem ODBC