Partilhar via


Fornecedores de Keystore Personalizados

Baixar driver ODBC

Visão geral

A funcionalidade de encriptação de colunas do SQL Server 2016 exige que as Chaves de Encriptação de Coluna Encriptadas (ECEKs) armazenadas no servidor sejam recuperadas pelo cliente e depois desencriptadas para Chaves de Encriptação de Coluna (CEKs) para aceder aos dados armazenados em colunas encriptadas. Os ECEKs são encriptados por Chaves Mestras de Coluna (CMKs), e a segurança do CMK é importante para a segurança da encriptação das colunas. Assim, a CMK deve ser armazenada num local seguro; o objetivo de um Fornecedor de Chaves de Encriptação de Coluna é fornecer uma interface que permita ao controlador ODBC aceder a estes CMKs armazenados de forma segura. Para utilizadores com armazenamento seguro próprio, a Interface de Fornecedor de Keystore Personalizada fornece uma estrutura para implementar o acesso ao armazenamento seguro do CMK para o driver ODBC, que pode depois ser usado para realizar encriptação e desencriptação do CEK.

Cada fornecedor de keystore contém e gere um ou mais CMKs, que são identificados por keypaths – cadeias de um formato definido pelo fornecedor. Este CMK, juntamente com o algoritmo de encriptação, também uma cadeia definida pelo fornecedor, pode ser usado para realizar a encriptação de um CEK e a desencriptação de um ECEK. O algoritmo, juntamente com o ECEK e o nome do fornecedor, são armazenados nos metadados de encriptação da base de dados. Para mais informações, consulte CRIAR CHAVE MESTRA DE COLUNA e CRIAR CHAVE DE ENCRIPTAÇÃO DE COLUNA. Assim, as duas operações fundamentais da gestão de chaves são:

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

-and-

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

onde o CEKeystoreProvider_name é usado para identificar o Fornecedor de Chaves de Encriptação de Coluna específico (CEKeystoreProvider), e os outros argumentos são usados pelo CEKeystoreProvider para encriptar/desencriptar o (E)CEK. O nome e o caminho da chave são fornecidos pelos metadados CMK, enquanto o algoritmo e o valor ECEK são fornecidos pelos metadados CEK. Podem existir múltiplos fornecedores de armazenamento de chaves juntamente com o(s) fornecedor(es) incorporado(s) padrão. Ao realizar uma operação que requer o CEK, o driver utiliza os metadados CMK para encontrar o fornecedor de keystore apropriado pelo nome e executa a sua operação de desencriptação, que pode ser expressa como:

CEK = CEKeyStoreProvider_specific_decrypt(Key_path, Key_algorithm, ECEK)

Embora o driver não precise de encriptar CEKs, uma ferramenta de gestão de chaves pode precisar de o fazer para implementar operações como a criação e rotação de CMK. Estas ações exigem a realização da operação inversa:

ECEK = CEKeyStoreProvider_specific_encrypt(Key_path, Key_algorithm, CEK)

Interface CEKeyStoreProvider

Este documento descreve em detalhe a interface CEKeyStoreProvider. Um fornecedor de keystore que implemente esta interface pode ser utilizado pelo Microsoft ODBC Driver for SQL Server. Os implementadores do CEKeyStoreProvider podem usar este guia para desenvolver fornecedores de keystore personalizados utilizáveis pelo driver.

Uma biblioteca de fornecedores de armazenamento de chaves ("biblioteca de fornecedores") é uma biblioteca de ligação dinâmica que pode ser carregada pelo controlador ODBC e contém um ou mais fornecedores de armazenamento de chaves. O símbolo CEKeystoreProvider deve ser exportado por uma biblioteca fornecedora e ser o endereço de um array terminado por null de ponteiros para estruturas CEKeystoreProvider, uma para cada fornecedor de armazenamento de chaves dentro da biblioteca.

Uma CEKeystoreProvider estrutura define os pontos de entrada de um único fornecedor de 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;
Nome do campo Description
Name O nome do fornecedor da loja de chaves. Não pode ser igual a qualquer outro fornecedor de armazenamento de chaves previamente carregado pelo driver ou presente nesta biblioteca. Cadeia de caracteres largos terminada por nula.
Init Função de inicialização. Se não for necessária uma função de inicialização, este campo pode ser nulo.
Read Função de leitura do provedor. Pode ser nulo se não for necessário.
Write Função de escrita do fornecedor. Obrigatório se Read não for nulo. Pode ser nulo se não for necessário.
DecryptCEK Função de desencriptação ECEK. Esta função é a razão de existir de um fornecedor de armazenamento de chaves e não pode ser nula.
EncryptCEK Função de encriptação CEK. O driver não chama esta função, mas ela é fornecida para permitir o acesso programático à criação de ECEK por ferramentas de gestão de chaves. Pode ser nulo se não for necessário.
Free Função de terminação. Pode ser nulo se não for necessário.

Exceto o Free, as funções nesta interface têm todas um par de parâmetros, ctx e onError. A primeira identifica o contexto em que a função é chamada, enquanto a segunda é usada para reportar erros. Para mais informações, consulte Contextos e Gestão de Erros abaixo.

int Init(CEKEYSTORECONTEXT *ctx, errFunc onError);

Nome provisório para uma função de inicialização definida pelo fornecedor. O driver chama esta função uma vez, depois de um provedor ter sido carregado, mas antes da primeira vez em que necessitar de realizar desencriptação ECEK ou pedidos de leitura/escrita. Use esta função para realizar qualquer inicialização necessária.

Argument Description
ctx [Entrada] Contexto da operação.
onError [Entrada] Função de reporte de erros.
Return Value Retorne um valor diferente de zero para indicar sucesso, ou zero para indicar fracasso.
int Read(CEKEYSTORECONTEXT *ctx, errFunc onError, void *data, unsigned int *len);

Nome provisório para uma função de comunicação definida pelo fornecedor. O driver chama esta função quando a aplicação pede para ler dados de um fornecedor (previamente escrito) usando o atributo SQL_COPT_SS_CEKEYSTOREDATA connection, permitindo que a aplicação leia dados arbitrários do fornecedor. Para mais informações, consulte Comunicação com Fornecedores de Keystores.

Argument Description
ctx [Entrada] Contexto da operação.
onError [Entrada] Função de reporte de erros.
data [Saída] Apontador para um buffer onde o fornecedor escreve dados para serem lidos pela aplicação. Este buffer corresponde ao campo de dados da estrutura CEKEYSTOREDATA.
len [InOut] Apontador para um valor de comprimento; Ao ser introduzido, este valor corresponde ao comprimento máximo do buffer de dados, e o fornecedor não deve escrever mais do *len que bytes nele. Após o retorno, o provedor deve atualizar *len com o número de bytes escritos.
Return Value Retorne um valor diferente de zero para indicar sucesso, ou zero para indicar fracasso.
int Write(CEKEYSTORECONTEXT *ctx, errFunc onError, void *data, unsigned int len);

Nome provisório para uma função de comunicação definida pelo fornecedor. O driver chama esta função quando a aplicação solicita a escrita de dados a um fornecedor usando o atributo de ligação SQL_COPT_SS_CEKEYSTOREDATA, permitindo que a aplicação grave dados arbitrários para o fornecedor. Para mais informações, consulte Comunicação com Fornecedores de Keystores.

Argument Description
ctx [Entrada] Contexto da operação.
onError [Entrada] Função de reporte de erros.
data [Entrada] Apontador para um buffer que contém os dados para o fornecedor ler. Este buffer corresponde ao campo de dados da estrutura CEKEYSTOREDATA. O fornecedor não deve ler mais do que len bytes deste buffer.
len [Entrada] O número de bytes disponíveis nos dados. Este valor corresponde ao campo dataSize da estrutura CEKEYSTOREDATA.
Return Value Retorne um valor diferente de zero para indicar sucesso, ou zero para indicar fracasso.
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);

Nome provisório para uma função de desencriptação ECEK definida pelo fornecedor. O driver chama esta função para desencriptar um ECEK encriptado por um CMK associado a este fornecedor num CEK.

Argument Description
ctx [Entrada] Contexto da operação.
onError [Entrada] Função de reporte de erros.
keyPath [Entrada] O valor do atributo de metadados KEY_PATH para a CMK referenciada pelo ECEK dado. Cadeia de caracteres largos terminada por nulo. Este valor destina-se a identificar uma CMK gerida por este fornecedor.
alg [Entrada] O valor do atributo de metadados ALGORITMO para o ECEK dado. Cadeia de caracteres larga terminada por caractere nulo. Este valor destina-se a identificar o algoritmo de encriptação usado para encriptar o ECEK dado.
ecek [Ponteiro] Ponteiro para o ECEK a ser decifrado.
ecekLen [Entrada] Comprimento do ECEK.
cekOut [Saída] O fornecedor deve alocar memória para o ECEK desencriptado e escrever o seu endereço no ponteiro apontado pelo cekOut. Deve ser possível libertar este bloco de memória usando a função LocalFree (Windows) ou free (Linux/macOS). Se não for alocada memória devido a um erro ou outro motivo, o fornecedor deve definir *cekOut como um ponteiro nulo.
cekLen [Saída] O fornecedor deverá escrever para o endereço apontado pelo cekLen o comprimento do ECEK desencriptado que escreveu para **cekOut.
Return Value Retorne um valor diferente de zero para indicar sucesso, ou zero para indicar fracasso.
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);

Nome provisório para uma função de encriptação CEK definida pelo fornecedor. O driver não chama esta função nem expõe a sua funcionalidade através da interface ODBC, mas é fornecido para permitir o acesso programático à criação ECEK por ferramentas de gestão de chaves.

Argument Description
ctx [Entrada] Contexto da operação.
onError [Entrada] Função de reporte de erros.
keyPath [Entrada] O valor do atributo de metadados KEY_PATH para a CMK referenciada pelo ECEK dado. Cadeia de caracteres largos* terminada por nulo. Este valor destina-se a identificar uma CMK gerida por este fornecedor.
alg [Entrada] O valor do atributo de metadados ALGORITMO para o ECEK dado. Cadeia de caracteres largos terminada por nulo. Este valor destina-se a identificar o algoritmo de encriptação usado para encriptar o ECEK dado.
cek [Entrada] Ponteiro para o CEK a ser encriptado.
cekLen [Entrada] Comprimento do CEK.
ecekOut [Saída] O fornecedor deve alocar memória para o CEK encriptado e escrever o seu endereço no ponteiro apontado pelo ecekOut. Deve ser possível libertar este bloco de memória usando a função LocalFree (Windows) ou free (Linux/macOS). Se não for alocada memória devido a um erro ou outro motivo, o fornecedor deverá definir *ecekOut como um ponteiro nulo.
ecekLen [Saída] O fornecedor deverá escrever, no endereço indicado por ecekLen, o comprimento do CEK encriptado que foi escrito em **ecekOut.
Return Value Retorne um valor diferente de zero para indicar sucesso, ou zero para indicar fracasso.
void (*Free)();

Nome provisório para uma função de terminação definida pelo fornecedor. O condutor pode chamar esta função após a terminação normal do processo.

Observação

As strings de caracteres largos são caracteres de 2 bytes (UTF-16) devido à forma como o SQL Server as armazena.

Tratamento de erros

Como podem ocorrer erros durante o processamento de um fornecedor, é fornecido um mecanismo que lhe permite reportar erros de volta ao controlador com mais detalhe específico do que um sucesso/falha booleano. Muitas das funções têm um par de parâmetros, ctx e onError, que são usados em conjunto para este propósito, além do valor de retorno de sucesso/falha.

O parâmetro ctx identifica o contexto em que ocorre uma operação de fornecedor.

O parâmetro onError aponta para uma função de reporte de erros, com o seguinte protótipo:

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

Argument Description
ctx [Entrada] O contexto sobre o qual relatar o erro.
msg [Entrada] A mensagem de erro a reportar. Cadeia de caracteres largos terminada por null. Para permitir informação parametrizada, esta cadeia pode conter sequências de inserção-formatação do formulário aceite pela função FormatMessage . A funcionalidade estendida pode ser especificada por este parâmetro conforme descrito abaixo.
... [Entrada] Parâmetros variáveis extra para ajustar os especificadores de formato na mensagem, conforme apropriado.

Para reportar quando ocorreu um erro, o fornecedor chama o onError, fornecendo o parâmetro de contexto passado para a função do fornecedor pelo driver e uma mensagem de erro com parâmetros adicionais opcionais a serem formatados. O fornecedor pode chamar esta função várias vezes para enviar múltiplas mensagens de erro consecutivamente dentro de uma única invocação da função do provedor. Por exemplo:

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

O msg parâmetro é normalmente uma cadeia de caracteres largos, mas existem mais extensões disponíveis:

Ao usar um dos valores especiais predefinidos com a macro IDS_MSG, podem ser utilizadas mensagens de erro genéricas já existentes e localizadas no driver. Por exemplo, se um fornecedor falhar em alocar memória, pode ser usada a IDS_S1_001 mensagem "Falha de alocação de memória":

onError(ctx, IDS_MSG(IDS_S1_001));

Para que o erro seja reconhecido pelo driver, a função fornecedora deve devolver a falha. Quando ocorre uma falha no contexto de uma operação ODBC, os erros publicados tornam-se acessíveis na ligação ou no handler da instrução através do mecanismo padrão de diagnóstico ODBC (SQLError, SQLGetDiagRec, e SQLGetDiagField).

Associação de Contexto

A CEKEYSTORECONTEXT estrutura, para além de fornecer contexto para o callback de erro, pode também ser usada para determinar o contexto ODBC em que uma operação de fornecedor é executada. Este contexto permite que um fornecedor associe dados a cada um destes contextos, por exemplo, para implementar uma configuração por ligação. Para tal, a estrutura contém três ponteiros opacos correspondentes ao ambiente, à ligação e ao contexto da afirmação:

typedef struct CEKeystoreContext
{
void *envCtx;
void *dbcCtx;
void *stmtCtx;
} CEKEYSTORECONTEXT;
Campo Description
envCtx Contexto ambiental.
dbcCtx Contexto de ligação.
stmtCtx Contexto da afirmação.

Cada um destes contextos é um valor opaco que, embora não seja o mesmo que o handle ODBC correspondente, pode ser usado como um identificador único para o handle: se o handle X estiver associado ao valor de contexto Y, então nenhum outro handler de ambiente, conexão ou instrução que exista simultaneamente com X terá um valor de contexto Y, e nenhum outro valor de contexto será associado ao handle X. Se a operação do fornecedor a ser realizada não tiver um contexto específico de handle (por exemplo, chamadas SQLSetConnectAttr para carregar e configurar providers, nas quais não existe handle de instruções), o valor de contexto correspondente na estrutura é nulo.

Example

Fornecedor de Keystores

O código seguinte é um exemplo de implementação de um fornecedor mínimo de armazenamento de chaves.

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

Aplicação ODBC

O código seguinte é uma aplicação de demonstração que utiliza o fornecedor de keystore acima. Ao executá-lo, certifique-se de que a biblioteca do fornecedor está no mesmo diretório do binário da aplicação, e que a cadeia de ligação especifica (ou especifica um DSN que contenha) a ColumnEncryption=Enabled definição.

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

Ver também

Usar o Always Encrypted com o Driver ODBC