Penyedia Keystore Kustom

Unduh driver ODBC

Gambaran Umum

Fitur enkripsi kolom SQL Server 2016 mengharuskan Kunci Enkripsi Kolom Terenkripsi (ECEK) yang disimpan di server diambil oleh klien lalu didekripsi ke Kunci Enkripsi Kolom (CEK) untuk mengakses data yang disimpan dalam kolom terenkripsi. ECEK dienkripsi oleh Kunci Master Kolom (CMK), dan keamanan CMK penting untuk keamanan enkripsi kolom. Dengan demikian, CMK harus disimpan di lokasi yang aman; tujuan dari Penyedia Keystore Enkripsi Kolom adalah untuk menyediakan antarmuka untuk memungkinkan driver ODBC mengakses CMK yang disimpan dengan aman ini. Untuk pengguna dengan penyimpanan aman mereka sendiri, Antarmuka Penyedia Keystore Kustom menyediakan kerangka kerja untuk menerapkan akses ke penyimpanan CMK yang aman untuk driver ODBC, yang kemudian dapat digunakan untuk melakukan enkripsi dan dekripsi CEK.

Setiap penyedia keystore berisi dan mengelola satu atau beberapa CMK, yang diidentifikasi oleh jalur kunci - string format yang ditentukan oleh penyedia. CMK ini, bersama dengan algoritma enkripsi, juga string yang ditentukan oleh penyedia, dapat digunakan untuk melakukan enkripsi CEK dan dekripsi ECEK. Algoritma, bersama dengan ECEK dan nama penyedia, disimpan dalam metadata enkripsi database. Untuk informasi selengkapnya, lihat MEMBUAT KUNCI MASTER KOLOM dan MEMBUAT KUNCI ENKRIPSI KOLOM. Dengan demikian, dua operasi mendasar manajemen kunci adalah:

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

-and-

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

CEKeystoreProvider_name di mana digunakan untuk mengidentifikasi Penyedia Keystore Enkripsi Kolom tertentu (CEKeystoreProvider), dan argumen lainnya digunakan oleh CEKeystoreProvider untuk mengenkripsi/mendekripsi (E)CEK. Nama dan jalur kunci disediakan oleh metadata CMK, sementara algoritma dan nilai ECEK disediakan oleh metadata CEK. Beberapa penyedia keystore mungkin ada bersama penyedia bawaan default. Setelah melakukan operasi yang memerlukan CEK, driver menggunakan metadata CMK untuk menemukan penyedia keystore yang sesuai berdasarkan nama, dan menjalankan operasi dekripsinya, yang dapat dinyatakan sebagai:

CEK = CEKeyStoreProvider_specific_decrypt(Key_path, Key_algorithm, ECEK)

Meskipun driver tidak perlu mengenkripsi CEK, alat manajemen kunci mungkin perlu melakukannya untuk menerapkan operasi seperti pembuatan dan rotasi CMK. Tindakan ini memerlukan melakukan operasi terbalik:

ECEK = CEKeyStoreProvider_specific_encrypt(Key_path, Key_algorithm, CEK)

Antarmuka CEKeyStoreProvider

Dokumen ini menjelaskan secara rinci antarmuka CEKeyStoreProvider. Penyedia keystore yang mengimplementasikan antarmuka ini dapat digunakan oleh Driver Microsoft ODBC untuk SQL Server. Pelaksana CEKeyStoreProvider dapat menggunakan panduan ini untuk mengembangkan penyedia keystore kustom yang dapat digunakan oleh driver.

Pustaka penyedia keystore ("pustaka penyedia") adalah pustaka tautan dinamis yang dapat dimuat oleh driver ODBC, dan berisi satu atau beberapa penyedia keystore. Simbol CEKeystoreProvider harus diekspor oleh pustaka penyedia, dan menjadi alamat array pointer yang dihentikan null ke CEKeystoreProvider struktur, satu untuk setiap penyedia keystore dalam pustaka.

CEKeystoreProvider Struktur mendefinisikan titik masuk dari satu penyedia keystore:

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;
Nama Bidang Deskripsi
Name Nama penyedia keystore. Ini tidak boleh sama dengan penyedia keystore lain yang sebelumnya dimuat oleh driver atau ada di pustaka ini. String karakter lebar* yang dihentikan null.
Init Fungsi inisialisasi. Jika fungsi inisialisasi tidak diperlukan, bidang ini mungkin null.
Read Fungsi baca penyedia. Mungkin null jika tidak diperlukan.
Write Fungsi tulis penyedia. Diperlukan jika Baca tidak null. Mungkin null jika tidak diperlukan.
DecryptCEK Fungsi dekripsi ECEK. Fungsi ini adalah alasan keberadaan penyedia keystore, dan tidak boleh null.
EncryptCEK Fungsi enkripsi CEK. Driver tidak memanggil fungsi ini, tetapi disediakan untuk memungkinkan akses terprogram ke pembuatan ECEK oleh alat manajemen kunci. Mungkin null jika tidak diperlukan.
Free Fungsi penghentian. Mungkin null jika tidak diperlukan.

Kecuali Gratis, fungsi dalam antarmuka ini semuanya memiliki sepasang parameter, ctx dan onError. Yang pertama mengidentifikasi konteks di mana fungsi dipanggil, sementara yang terakhir digunakan untuk melaporkan kesalahan. Untuk informasi selengkapnya, lihat Konteks dan Penanganan Kesalahan di bawah ini.

int Init(CEKEYSTORECONTEXT *ctx, errFunc onError);

Nama tempat penampung untuk fungsi inisialisasi yang ditentukan penyedia. Driver memanggil fungsi ini sekali, setelah penyedia dimuat, tetapi sebelum pertama kali membutuhkannya untuk melakukan dekripsi ECEK atau permintaan Read()/Write(). Gunakan fungsi ini untuk melakukan inisialisasi apa pun yang dibutuhkannya.

Argumen Deskripsi
ctx [Input] Konteks operasi.
onError [Input] Fungsi pelaporan kesalahan.
Return Value Kembalikan bukan nol untuk menunjukkan keberhasilan, atau nol untuk menunjukkan kegagalan.
int Read(CEKEYSTORECONTEXT *ctx, errFunc onError, void *data, unsigned int *len);

Nama tempat penampung untuk fungsi komunikasi yang ditentukan penyedia. Driver memanggil fungsi ini ketika aplikasi meminta untuk membaca data dari penyedia (sebelumnya ditulis ke) menggunakan atribut koneksi SQL_COPT_SS_CEKEYSTOREDATA, memungkinkan aplikasi membaca data arbitrer dari penyedia. Untuk informasi selengkapnya, lihat Berkomunikasi dengan Penyedia Keystore.

Argumen Deskripsi
ctx [Input] Konteks operasi.
onError [Input] Fungsi pelaporan kesalahan.
data [Output] Penunjuk ke buffer tempat penyedia menulis data untuk dibaca oleh aplikasi. Buffer ini sesuai dengan bidang data struktur CEKEYSTOREDATA.
len [InOut] Penunjuk ke nilai panjang; setelah input, nilai ini adalah panjang maksimum buffer data, dan penyedia tidak akan menulis lebih dari *len byte ke dalamnya. Setelah kembali, penyedia harus memperbarui *len dengan jumlah byte yang ditulis.
Return Value Kembalikan bukan nol untuk menunjukkan keberhasilan, atau nol untuk menunjukkan kegagalan.
int Write(CEKEYSTORECONTEXT *ctx, errFunc onError, void *data, unsigned int len);

Nama tempat penampung untuk fungsi komunikasi yang ditentukan penyedia. Driver memanggil fungsi ini ketika aplikasi meminta untuk menulis data ke penyedia menggunakan atribut koneksi SQL_COPT_SS_CEKEYSTOREDATA, memungkinkan aplikasi untuk menulis data arbitrer ke penyedia. Untuk informasi selengkapnya, lihat Berkomunikasi dengan Penyedia Keystore.

Argumen Deskripsi
ctx [Input] Konteks operasi.
onError [Input] Fungsi pelaporan kesalahan.
data [Input] Penunjuk ke buffer yang berisi data untuk dibaca penyedia. Buffer ini sesuai dengan bidang data struktur CEKEYSTOREDATA. Penyedia tidak boleh membaca lebih dari byte len dari buffer ini.
len [Input] Jumlah byte yang tersedia dalam data. Nilai ini sesuai dengan bidang dataSize dari struktur CEKEYSTOREDATA.
Return Value Kembalikan bukan nol untuk menunjukkan keberhasilan, atau nol untuk menunjukkan kegagalan.
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);

Nama tempat penampung untuk fungsi dekripsi ECEK yang ditentukan penyedia. Driver memanggil fungsi ini untuk mendekripsi ECEK yang dienkripsi oleh CMK yang terkait dengan penyedia ini ke dalam CEK.

Argumen Deskripsi
ctx [Input] Konteks operasi.
onError [Input] Fungsi pelaporan kesalahan.
keyPath [Input] Nilai atribut metadata KEY_PATH untuk CMK yang dirujuk oleh ECEK yang diberikan. String karakter lebar* yang dihentikan null. Nilai ini dimaksudkan untuk mengidentifikasi CMK yang ditangani oleh penyedia ini.
alg [Input] Nilai atribut metadata ALGORITHM untuk ECEK yang diberikan. String karakter lebar* yang dihentikan null. Nilai ini dimaksudkan untuk mengidentifikasi algoritma enkripsi yang digunakan untuk mengenkripsi ECEK yang diberikan.
ecek [Input] Arahkan ke ECEK untuk didekripsi.
ecekLen [Input] Panjang ECEK.
cekOut [Output] Penyedia harus mengalokasikan memori untuk ECEK yang didekripsi dan menulis alamatnya ke pointer yang ditunjukkan oleh cekOut. Anda harus dapat membebaskan blok memori ini menggunakan fungsi LocalFree (Windows) atau gratis (Linux/macOS). Jika tidak ada memori yang dialokasikan karena kesalahan atau sebaliknya, penyedia akan mengatur *cekOut ke pointer null.
cekLen [Output] Penyedia harus menulis ke alamat yang ditunjukkan oleh cekLen panjang ECEK yang didekripsi yang telah ditulis ke **cekOut.
Return Value Kembalikan bukan nol untuk menunjukkan keberhasilan, atau nol untuk menunjukkan kegagalan.
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);

Nama tempat penampung untuk fungsi enkripsi CEK yang ditentukan penyedia. Driver tidak memanggil fungsi ini atau mengekspos fungsinya melalui antarmuka ODBC, tetapi disediakan untuk memungkinkan akses terprogram ke pembuatan ECEK oleh alat manajemen kunci.

Argumen Deskripsi
ctx [Input] Konteks operasi.
onError [Input] Fungsi pelaporan kesalahan.
keyPath [Input] Nilai atribut metadata KEY_PATH untuk CMK yang dirujuk oleh ECEK yang diberikan. String karakter lebar* yang dihentikan null. Nilai ini dimaksudkan untuk mengidentifikasi CMK yang ditangani oleh penyedia ini.
alg [Input] Nilai atribut metadata ALGORITHM untuk ECEK yang diberikan. String karakter lebar* yang dihentikan null. Nilai ini dimaksudkan untuk mengidentifikasi algoritma enkripsi yang digunakan untuk mengenkripsi ECEK yang diberikan.
cek [Input] Arahkan ke CEK yang akan dienkripsi.
cekLen [Input] Panjang CEK.
ecekOut [Output] Penyedia harus mengalokasikan memori untuk CEK terenkripsi dan menulis alamatnya ke pointer yang ditujukan oleh ecekOut. Anda harus dapat membebaskan blok memori ini menggunakan fungsi LocalFree (Windows) atau gratis (Linux/macOS). Jika tidak ada memori yang dialokasikan karena kesalahan atau sebaliknya, penyedia akan mengatur *ecekOut ke pointer null.
ecekLen [Output] Penyedia harus menulis ke alamat yang ditujukan oleh ecekLen panjang CEK terenkripsi yang telah ditulis ke **ecekOut.
Return Value Kembalikan bukan nol untuk menunjukkan keberhasilan, atau nol untuk menunjukkan kegagalan.
void (*Free)();

Nama tempat penampung untuk fungsi penghentian yang ditentukan penyedia. Driver dapat memanggil fungsi ini setelah penghentian normal proses.

Catatan

String karakter lebar adalah karakter 2 byte (UTF-16) karena bagaimana SQL Server menyimpannya.

Penanganan Kesalahan

Karena kesalahan dapat terjadi selama pemrosesan penyedia, mekanisme disediakan untuk memungkinkannya melaporkan kesalahan kembali ke driver secara lebih spesifik daripada keberhasilan/kegagalan boolean. Banyak fungsi memiliki sepasang parameter, ctx dan onError, yang digunakan bersama untuk tujuan ini selain nilai pengembalian keberhasilan/kegagalan.

Parameter ctx mengidentifikasi konteks di mana operasi penyedia terjadi.

Parameter onError menunjuk ke fungsi pelaporan kesalahan, dengan prototipe berikut:

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

Argumen Deskripsi
ctx [Input] Konteks untuk melaporkan kesalahan.
msg [Input] Pesan kesalahan untuk dilaporkan. String karakter lebar yang dihentikan null. Untuk mengizinkan informasi berparameter, string ini mungkin berisi urutan pemformatan sisipan formulir yang diterima oleh fungsi FormatMessage . Fungsionalitas yang diperluas dapat ditentukan oleh parameter ini seperti yang dijelaskan di bawah ini.
... [Input] Parameter variadik ekstra agar sesuai dengan penentu format dalam msg, sebagaimana melengkapi.

Untuk melaporkan ketika terjadi kesalahan, penyedia memanggil onError, menyediakan parameter konteks yang diteruskan ke fungsi penyedia oleh driver dan pesan kesalahan dengan parameter tambahan opsional untuk diformat di dalamnya. Penyedia dapat memanggil fungsi ini beberapa kali untuk memposting beberapa pesan kesalahan berturut-turut dalam satu pemanggilan fungsi penyedia. Contohnya:

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

Parameter msg biasanya merupakan string karakter lebar, tetapi lebih banyak ekstensi tersedia:

Dengan menggunakan salah satu nilai khusus yang telah ditentukan sebelumnya dengan makro IDS_MSG, pesan kesalahan umum yang sudah ada dan dalam formulir yang dilokalkan di driver dapat digunakan. Misalnya, jika penyedia gagal mengalokasikan memori, IDS_S1_001 pesan "Kegagalan alokasi memori" dapat digunakan:

onError(ctx, IDS_MSG(IDS_S1_001));

Agar kesalahan dikenali oleh driver, fungsi penyedia harus mengembalikan kegagalan. Ketika kegagalan terjadi dalam konteks operasi ODBC, kesalahan yang diposting akan dapat diakses pada pegangan koneksi atau pernyataan melalui mekanisme diagnostik ODBC standar (SQLError, , SQLGetDiagRecdan SQLGetDiagField).

Asosiasi Konteks

Struktur, CEKEYSTORECONTEXT selain memberikan konteks untuk panggilan balik kesalahan, juga dapat digunakan untuk menentukan konteks ODBC di mana operasi penyedia dijalankan. Konteks ini memungkinkan penyedia untuk mengaitkan data ke masing-masing konteks ini, misalnya, untuk menerapkan konfigurasi per koneksi. Untuk tujuan ini, struktur berisi tiga pointer buram yang sesuai dengan lingkungan, koneksi, dan konteks pernyataan:

typedef struct CEKeystoreContext
{
void *envCtx;
void *dbcCtx;
void *stmtCtx;
} CEKEYSTORECONTEXT;
Bidang Deskripsi
envCtx Konteks lingkungan.
dbcCtx Konteks koneksi.
stmtCtx Konteks pernyataan.

Masing-masing konteks ini adalah nilai buram yang, meskipun tidak sama dengan handel ODBC yang sesuai, dapat digunakan sebagai pengidentifikasi unik untuk handel: jika handel X dikaitkan dengan nilai konteks Y, maka tidak ada lingkungan, koneksi, atau handel pernyataan lain yang ada secara bersamaan pada saat yang sama karena X akan memiliki nilai konteks Y, dan tidak ada nilai konteks lain yang akan dikaitkan dengan handel X. Jika operasi penyedia yang dicapai tidak memiliki konteks handel tertentu (misalnya, SQLSetConnectAttr memanggil untuk memuat dan mengonfigurasi penyedia, di mana tidak ada pegangan pernyataan), nilai konteks yang sesuai dalam struktur adalah null.

Contoh

Penyedia Keystore

Kode berikut adalah contoh implementasi penyedia keystore minimal.

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

Aplikasi ODBC

Kode berikut adalah aplikasi demo yang menggunakan penyedia keystore di atas. Saat menjalankannya, pastikan bahwa pustaka penyedia berada dalam direktori yang sama dengan biner aplikasi, dan bahwa string koneksi menentukan (atau menentukan DSN yang berisi) ColumnEncryption=Enabled pengaturan.

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

Lihat juga

Menggunakan Always Encrypted dengan Driver ODBC