Not
Åtkomst till denna sida kräver auktorisation. Du kan prova att logga in eller byta katalog.
Åtkomst till denna sida kräver auktorisation. Du kan prova att byta katalog.
Översikt
Kolumnkrypteringsfunktionen i SQL Server 2016 kräver att de krypterade kolumnkrypteringsnycklar (ECEK:er) som lagras på servern hämtas av klienten och sedan dekrypteras till kolumnkrypteringsnycklar (CEK:er) för att få åtkomst till data som lagras i krypterade kolumner. ECEK:er krypteras med kolumnhuvudnycklar (CMK:er) och säkerheten för CMK är viktig för säkerheten för kolumnkryptering. Cmk bör därför lagras på en säker plats. Syftet med en nyckellagringsprovider för kolumnkryptering är att tillhandahålla ett gränssnitt som gör det möjligt för ODBC-drivrutinen att komma åt dessa säkert lagrade CMK:er. För användare med egen säker lagring tillhandahåller Custom Keystore Provider Interface ett ramverk för att implementera åtkomst till säker lagring av CMK för ODBC-drivrutinen, som sedan kan användas för att utföra CEK-kryptering och dekryptering.
Varje nyckellagringsprovider innehåller och hanterar en eller flera CMK:er, som identifieras av nyckelsökvägar – strängar i ett format som definierats av providern. Denna CMK, tillsammans med krypteringsalgoritmen, även en sträng som definierats av providern, kan användas för att utföra kryptering av en CEK och dekryptering av en ECEK. Algoritmen, tillsammans med ECEK och namnet på providern, lagras i databasens krypteringsmetadata. Mer information finns i SKAPA KOLUMNHUVUDNYCKEL och SKAPA KOLUMNKRYPTERINGSNYCKEL. Därför är de två grundläggande åtgärderna för nyckelhantering:
CEK = DecryptViaCEKeystoreProvider(CEKeystoreProvider_name, Key_path, Key_algorithm, ECEK)
-and-
ECEK = EncryptViaCEKeystoreProvider(CEKeyStoreProvider_name, Key_path, Key_algorithm, CEK)
CEKeystoreProvider_name där används för att identifiera den specifika nyckellagringsprovidern för kolumnkryptering (CEKeystoreProvider) och de andra argumenten används av CEKeystoreProvider för att kryptera/dekryptera (E)CEK. Namn- och nyckelsökvägen tillhandahålls av CMK-metadata, medan algoritmen och ECEK-värdet tillhandahålls av CEK-metadata. Flera keystore-leverantörer kan finnas tillsammans med de inbyggda standardleverantörerna. När du utför en åtgärd som kräver CEK använder drivrutinen CMK-metadata för att hitta lämplig nyckellagringsprovider efter namn och kör dess dekrypteringsåtgärd, som kan uttryckas som:
CEK = CEKeyStoreProvider_specific_decrypt(Key_path, Key_algorithm, ECEK)
Även om drivrutinen inte behöver kryptera CEK:er kan ett nyckelhanteringsverktyg behöva göra det för att implementera åtgärder som att skapa och rotera CMK. Dessa åtgärder kräver att du utför den omvända åtgärden:
ECEK = CEKeyStoreProvider_specific_encrypt(Key_path, Key_algorithm, CEK)
CEKeyStoreProvider-gränssnitt
Det här dokumentet beskriver i detalj CEKeyStoreProvider-gränssnittet. En nyckellagringsprovider som implementerar det här gränssnittet kan användas av Microsoft ODBC-drivrutinen för SQL Server. CEKeyStoreProvider-implementerare kan använda den här guiden för att utveckla anpassade nyckellagringsleverantörer som kan användas av drivrutinen.
Ett nyckelarkivproviderbibliotek ("providerbiblioteket") är ett dynamiskt länkbibliotek som kan läsas in av ODBC-drivrutinen och som innehåller en eller flera nyckellagringsproviders. Symbolen CEKeystoreProvider måste exporteras av ett providerbibliotek och vara adressen till en null-avslutad matris med pekare till CEKeystoreProvider strukturer, en för varje nyckellagringsprovider i biblioteket.
En CEKeystoreProvider struktur definierar startpunkterna för en enda nyckellagringsprovider:
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;
| Fältnamn | Description |
|---|---|
Name |
Namnet på nyckellagringsprovidern. Den får inte vara samma som någon annan nyckellagringsprovider som tidigare lästs in av drivrutinen eller som finns i det här biblioteket. Null-avslutad sträng med brett tecken* . |
Init |
Initieringsfunktion. Om en initieringsfunktion inte krävs kan det här fältet vara null. |
Read |
Providerns läsfunktion. Kan vara null om det inte behövs. |
Write |
Providerns skrivfunktion. Krävs om Read inte är null. Kan vara null om det inte behövs. |
DecryptCEK |
ECEK-dekrypteringsfunktion. Den här funktionen är orsaken till att det finns en nyckellagringsprovider och får inte vara null. |
EncryptCEK |
CEK-krypteringsfunktion. Drivrutinen anropar inte den här funktionen, men den tillhandahålls för programmatisk åtkomst till skapande av ECEK via nyckelhanteringsverktyg. Kan vara null om det inte behövs. |
Free |
Avslutningsfunktion. Kan vara null om det inte behövs. |
Förutom Free har alla funktioner i det här gränssnittet ett par parametrar, ctx och onError. Den förra identifierar kontexten där funktionen anropas, medan den senare används för att rapportera fel. Mer information finns i Kontexter och Felhantering nedan.
int Init(CEKEYSTORECONTEXT *ctx, errFunc onError);
Platshållarnamn för en providerdefinierad initieringsfunktion. Drivrutinen anropar den här funktionen en gång efter att en provider har lästs in, men före första gången den behöver använda den för att utföra ECEK-dekryptering eller Read()/Write()-förfrågningar. Använd den här funktionen för att utföra alla initieringar som behövs.
| Argument | Description |
|---|---|
ctx |
[Indata] Driftkontext. |
onError |
[Indata] Felrapporteringsfunktion. |
Return Value |
Returnera ett icke-nollvärde för att indikera framgång, eller noll för att indikera fel. |
int Read(CEKEYSTORECONTEXT *ctx, errFunc onError, void *data, unsigned int *len);
Platshållarnamn för en providerdefinierad kommunikationsfunktion. Drivrutinen anropar den här funktionen när programmet begär att läsa data från en (tidigare skriven till) provider med hjälp av SQL_COPT_SS_CEKEYSTOREDATA anslutningsattribut, vilket gör att programmet kan läsa godtyckliga data från providern. Mer information finns i Kommunicera med Keystore-leverantörer.
| Argument | Description |
|---|---|
ctx |
[Indata] Åtgärdskontext. |
onError |
[Indata] Felrapporteringsfunktion. |
data |
[Utdata] Pekare till en buffert där providern skriver data som ska läsas av programmet. Den här bufferten motsvarar datafältet i CEKEYSTOREDATA-strukturen. |
len |
[InOut] Pekare till ett längdvärde; vid inmatning är det här värdet den maximala längden på databufferten, och leverantören får inte skriva mer än *len byte. När den återvänder bör leverantören uppdatera *len med antalet skrivna byte. |
Return Value |
Returnera ett tal som inte är noll för att indikera framgång, eller noll för att indikera fel. |
int Write(CEKEYSTORECONTEXT *ctx, errFunc onError, void *data, unsigned int len);
Platshållarnamn för en providerdefinierad kommunikationsfunktion. Drivrutinen anropar den här funktionen när programmet begär att skriva data till en provider med hjälp av anslutningsattributet SQL_COPT_SS_CEKEYSTOREDATA, vilket gör att programmet kan skriva godtyckliga data till providern. Mer information finns i Kommunicera med Keystore-leverantörer.
| Argument | Description |
|---|---|
ctx |
[Indata] Åtgärdskontext. |
onError |
Felrapporteringsfunktion. |
data |
[Indata] Pekare till en buffert som innehåller data för tjänsteleverantören att läsa. Den här bufferten motsvarar datafältet i CEKEYSTOREDATA-strukturen. Providern får inte läsa mer än len bytes från den här bufferten. |
len |
Antalet byte som är tillgängliga i datan. Det här värdet motsvarar fältet dataSize i CEKEYSTOREDATA-strukturen. |
Return Value |
Returnera ett icke-nollvärde för att indikera framgång eller noll för att indikera fel. |
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);
Platshållarnamn för en providerdefinierad ECEK-dekrypteringsfunktion. Drivrutinen anropar denna funktion för att dekryptera en ECEK som har krypterats med en CMK kopplad till denna leverantör, till en CEK.
| Argument | Description |
|---|---|
ctx |
[Indata] Åtgärdskontext. |
onError |
[Indata] Felrapporteringsfunktion. |
keyPath |
Indata: Värdet för KEY_PATH-metadata-attributet för CMK som refereras av det angivna ECEK. Null-avslutad wide-character*-sträng. Det här värdet är avsett att identifiera en CMK som hanteras av den här providern. |
alg |
[Indata] Värdet för ALGORITHM-attributet i metadata för den angivna ECEK. Null-avslutad wide-character*-sträng. Det här värdet är avsett att identifiera krypteringsalgoritmen som används för att kryptera den angivna ECEK. |
ecek |
[Indata] Pekare (pointer) till ECEK som ska dekrypteras. |
ecekLen |
[Indata] Längden på ECEK. |
cekOut |
[Utdata] Leverantören ska allokera minne för den dekrypterade ECEK och skriva sin adress till pekaren som cekOut pekar på. Det måste vara möjligt att frigöra det här minnesblocket med hjälp av funktionen LocalFree (Windows) eller kostnadsfritt (Linux/macOS). Om inget minne allokerades på grund av ett fel eller på annat sätt ska providern ange *cekOut till en null-pekare. |
cekLen |
[Utdata] Leverantören ska till adressen som cekLen hänvisar till skriva längden på den dekrypterade ECEK som den har skrivit till **cekOut. |
Return Value |
Returnera ett icke-nollvärde för att indikera framgång, eller noll för att indikera fel. |
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);
Platshållarnamn för en providerdefinierad CEK-krypteringsfunktion. Drivrutinen anropar inte den här funktionen och dess funktionalitet exponeras inte via ODBC-gränssnittet, men den tillhandahålls för programmatisk åtkomst till ECEK-skapande av nyckelhanteringsverktyg.
| Argument | Description |
|---|---|
ctx |
[Indata] Driftkontext. |
onError |
Inmatningsfelrapporteringsfunktion. |
keyPath |
[Indata] Värdet för KEY_PATH metadataattributet för CMK som refereras av den specificerade ECEK. Null-avslutad wide-character*-sträng. Det här värdet är avsett att identifiera en CMK som hanteras av den här providern. |
alg |
[Indata] Värdet av attributet för ALGORITHM metadata för den angivna ECEK. Null-avslutad wide-character*-sträng. Det här värdet är avsett att identifiera krypteringsalgoritmen som används för att kryptera den angivna ECEK. |
cek |
[Indata] En pekare till den CEK som ska krypteras. |
cekLen |
[Indata] Längden på CEK. |
ecekOut |
[Utdata] Leverantören ska allokera minne för den krypterade CEK och skriva dess adress till den pekare som ecekOut pekar på. Det måste vara möjligt att frigöra det här minnesblocket med hjälp av funktionen LocalFree (Windows) eller kostnadsfritt (Linux/macOS). Om inget minne allokerades på grund av ett fel eller på annat sätt ska providern ange *ecekOut till en null-pekare. |
ecekLen |
[Utdata] Leverantören ska skriva längden på den krypterade CEK till den adress som ecekLen pekar på och som den har skrivit till **ecekOut. |
Return Value |
Returnera ett icke-nollvärde för att indikera framgång, eller noll för att indikera fel. |
void (*Free)();
Platshållarnamn för en av en leverantör definierad avslutningsfunktion. Drivrutinen kan anropa den här funktionen vid normal avslutning av processen.
Anmärkning
Breda teckensträngar är tecken med 2 byte i UTF-16-format på grund av hur SQL Server lagrar dem.
Felhantering
Eftersom fel kan uppstå under en leverantörs bearbetning tillhandahålls en mekanism som gör det möjligt att rapportera fel tillbaka till drivrutinen i mer specifik detalj än ett booleskt värde av lyckat/misslyckat. Många av funktionerna har ett par parametrar, ctx och onError, som används tillsammans för detta ändamål utöver returvärdet för lyckade/misslyckade.
Parametern ctx identifierar kontexten där en provideråtgärd inträffar.
Parametern onError pekar på en felrapporteringsfunktion med följande prototyp:
typedef void errFunc(CEKEYSTORECONTEXT *ctx, const wchar_t *msg, ...);
| Argument | Description |
|---|---|
ctx |
[Indata] Kontexten som felet ska rapporteras för. |
msg |
[Indata] Felmeddelandet som ska rapporteras. Null-avslutad sträng med breda tecken. För att tillåta parametriserad information kan den här strängen innehålla infogningsformateringssekvenser för formuläret som godkänts av funktionen FormatMessage . Utökade funktioner kan anges av den här parametern enligt beskrivningen nedan. |
| ... | [Indata] Extra parametrar av variabel längd som matchar formatspecificerare i meddelandet, när det är lämpligt. |
För att rapportera när ett fel har inträffat anropar providern onError och anger kontextparametern som skickas till providerfunktionen av drivrutinen och ett felmeddelande med valfria extra parametrar som ska formateras i den. Providern kan anropa den här funktionen flera gånger för att publicera flera felmeddelanden i följd inom ett providerfunktionsanrop. Till exempel:
if (!doSomething(...))
{
onError(ctx, L"An error occurred in doSomething.");
onError(ctx, L"Additional error message with more details.");
return 0;
}
Parametern msg är vanligtvis en sträng med många tecken, men fler tillägg är tillgängliga:
Genom att använda något av de särskilda fördefinierade värdena med makrot IDS_MSG kan allmänna felmeddelanden som redan finns och i ett lokaliserat format i drivrutinen användas. Om en provider till exempel inte allokerar minne IDS_S1_001 kan meddelandet "Minnesallokeringsfel" användas:
onError(ctx, IDS_MSG(IDS_S1_001));
För att felet ska identifieras av drivrutinen måste providerfunktionen returnera fel. När ett fel inträffar i kontexten för en ODBC-åtgärd blir de publicerade felen tillgängliga i anslutnings- eller instruktionshandtaget via standardmetoden för ODBC-diagnostik (SQLError, SQLGetDiagRecoch SQLGetDiagField).
Kontextassociation
Strukturen CEKEYSTORECONTEXT, förutom att tillhandahålla kontext för felåteranrop, kan också användas för att fastställa ODBC-kontexten där en provider-operation körs. Med den här kontexten kan en provider associera data med var och en av dessa kontexter, till exempel för att implementera konfiguration per anslutning. För detta ändamål innehåller strukturen tre ogenomskinliga pekare som motsvarar miljön, anslutningen och instruktionskontexten:
typedef struct CEKeystoreContext
{
void *envCtx;
void *dbcCtx;
void *stmtCtx;
} CEKEYSTORECONTEXT;
| Fält | Description |
|---|---|
envCtx |
Miljökontext. |
dbcCtx |
Anslutningskontext. |
stmtCtx |
Uttalande kontext. |
Var och en av dessa kontexter är ett ogenomskinligt värde som, även om det inte är samma som motsvarande ODBC-handtag, kan användas som en unik identifierare för handtaget: om handtaget X är associerat med kontextvärdet Y, så har ingen annan miljö, anslutning eller instruktionshandtag som finns samtidigt som X har ett kontextvärde på Y, och inga andra kontextvärden kommer att associeras med handtaget X. Om provideråtgärden som utförs saknar en viss referenskontext (till exempel SQLSetConnectAttr-anrop för att läsa in och konfigurera providers, där det inte finns något instruktionshandtag), är motsvarande kontextvärde i strukturen null.
Example
Nyckellagerleverantör
Följande kod är ett exempel på en minimal implementering av nyckellagringsprovidern.
/* 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
};
ODBC-applikation
Följande kod är ett demoprogram som använder nyckellagringsprovidern ovan. När du kör det, kontrollera att leverantörsbiblioteket finns i samma katalog som programbinärfilen och att anslutningssträngen specificerar ColumnEncryption=Enabled-inställningen (eller specificerar ett DSN som innehåller det).
/*
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;
}