aboutsummaryrefslogtreecommitdiffstats
path: root/Plugins/DbSqliteSystemData/systemdata_crypt.c
blob: 67ec475a6f7eb1fc2c7a1e687274799c964bb05f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
/********************************************************
 * ADO.NET 2.0 Data Provider for SQLite Version 3.X
 * Written by Robert Simpson (robert@blackcastlesoft.com)
 *
 * Released to the public domain, use at your own risk!
 ********************************************************/

#ifndef SQLITE_OMIT_DISKIO
#ifdef SQLITE_HAS_CODEC

#include <windows.h>
#include <wincrypt.h>

/* Extra padding before and after the cryptographic buffer */
#define CRYPT_OFFSET 8

typedef struct _CRYPTBLOCK
{
  Pager    *pPager;       /* Pager this cryptblock belongs to */
  HCRYPTKEY hReadKey;     /* Key used to read from the database and write to the journal */
  HCRYPTKEY hWriteKey;    /* Key used to write to the database */
  DWORD     dwPageSize;   /* Size of pages */
  LPVOID    pvCrypt;      /* A buffer for encrypting/decrypting (if necessary) */
  DWORD     dwCryptSize;  /* Equal to or greater than dwPageSize.  If larger, pvCrypt is valid and this is its size */
} CRYPTBLOCK, *LPCRYPTBLOCK;

HCRYPTPROV g_hProvider = 0; /* Global instance of the cryptographic provider */

#define SQLITECRYPTERROR_PROVIDER "Cryptographic provider not available"

/* Needed for re-keying */
static void * systemdata_sqlite3pager_get_codecarg(Pager *pPager)
{
  return (pPager->xCodec) ? pPager->pCodec: NULL;
}

/* Create a cryptographic context.  Use the enhanced provider because it is available on
** most platforms
*/
static BOOL InitializeProvider()
{
  MUTEX_LOGIC( systemdata_sqlite3_mutex *pMaster = systemdata_sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER); )
  systemdata_sqlite3_mutex_enter(pMaster);

  if (g_hProvider)
  {
    systemdata_sqlite3_mutex_leave(pMaster);
    return TRUE;
  }

  if (!CryptAcquireContext(&g_hProvider, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
  {
    systemdata_sqlite3_mutex_leave(pMaster);
    return FALSE;
  }

  systemdata_sqlite3_mutex_leave(pMaster);
  return TRUE;
}

/* Create or update a cryptographic context for a pager.
** This function will automatically determine if the encryption algorithm requires
** extra padding, and if it does, will create a temp buffer big enough to provide
** space to hold it.
*/
static LPCRYPTBLOCK CreateCryptBlock(HCRYPTKEY hKey, Pager *pager, int pageSize, LPCRYPTBLOCK pExisting)
{
  LPCRYPTBLOCK pBlock;

  if (!pExisting) /* Creating a new cryptblock */
  {
    pBlock = systemdata_sqlite3_malloc(sizeof(CRYPTBLOCK));
    if (!pBlock) return NULL;

    ZeroMemory(pBlock, sizeof(CRYPTBLOCK));
    pBlock->hReadKey = hKey;
    pBlock->hWriteKey = hKey;
  }
  else /* Updating an existing cryptblock */
  {
    pBlock = pExisting;
  }

  if (pageSize == -1)
    pageSize = pager->pageSize;

  pBlock->pPager = pager;
  pBlock->dwPageSize = (DWORD)pageSize;
  pBlock->dwCryptSize = pBlock->dwPageSize;

  /* Existing cryptblocks may have a buffer, if so, delete it */
  if (pBlock->pvCrypt)
  {
    systemdata_sqlite3_free(pBlock->pvCrypt);
    pBlock->pvCrypt = NULL;
  }

  /* Figure out how big to make our spare crypt block */
  CryptEncrypt(hKey, 0, TRUE, 0, NULL, &pBlock->dwCryptSize, pBlock->dwCryptSize * 2);
  pBlock->pvCrypt = systemdata_sqlite3_malloc(pBlock->dwCryptSize + (CRYPT_OFFSET * 2));
  if (!pBlock->pvCrypt)
  {
    /* We created a new block in here, so free it.  Otherwise leave the original intact */
    if (pBlock != pExisting)
      systemdata_sqlite3_free(pBlock);

    return NULL;
  }

  return pBlock;
}

/* Destroy a cryptographic context and any buffers and keys allocated therein */
static void systemdata_sqlite3CodecFree(LPVOID pv)
{
  LPCRYPTBLOCK pBlock = (LPCRYPTBLOCK)pv;
  /* Destroy the read key if there is one */
  if (pBlock->hReadKey)
  {
    CryptDestroyKey(pBlock->hReadKey);
  }

  /* If there's a writekey and its not equal to the readkey, destroy it */
  if (pBlock->hWriteKey && pBlock->hWriteKey != pBlock->hReadKey)
  {
    CryptDestroyKey(pBlock->hWriteKey);
  }

  /* If there's extra buffer space allocated, free it as well */
  if (pBlock->pvCrypt)
  {
    systemdata_sqlite3_free(pBlock->pvCrypt);
  }

  /* All done with this cryptblock */
  systemdata_sqlite3_free(pBlock);
}

void systemdata_sqlite3CodecSizeChange(void *pArg, int pageSize, int reservedSize)
{
  LPCRYPTBLOCK pBlock = (LPCRYPTBLOCK)pArg;

  if (pBlock->dwPageSize != pageSize)
  {
    CreateCryptBlock(pBlock->hReadKey, pBlock->pPager, pageSize, pBlock);
    /* If this fails, pvCrypt will be NULL, and the next time systemdata_sqlite3Codec() is called, it will result in an error */
  }
}

/* Encrypt/Decrypt functionality, called by pager.c */
static void *systemdata_sqlite3Codec(void *pArg, void *data, Pgno nPageNum, int nMode)
{
  LPCRYPTBLOCK pBlock = (LPCRYPTBLOCK)pArg;
  DWORD dwPageSize;
  LPVOID pvTemp = NULL;

  if (!pBlock) return data;
  if (pBlock->pvCrypt == NULL) return NULL; /* This only happens if CreateCryptBlock() failed to make scratch space */

  switch(nMode)
  {
  case 0: /* Undo a "case 7" journal file encryption */
  case 2: /* Reload a page */
  case 3: /* Load a page */
    if (!pBlock->hReadKey) break;

    /* Block ciphers often need to write extra padding beyond the
    data block.  We don't have that luxury for a given page of data so
    we must copy the page data to a buffer that IS large enough to hold
    the padding.  We then encrypt the block and write the buffer back to
    the page without the unnecessary padding.
    We only use the special block of memory if its absolutely necessary. */
    if (pBlock->dwCryptSize != pBlock->dwPageSize)
    {
      CopyMemory(((LPBYTE)pBlock->pvCrypt) + CRYPT_OFFSET, data, pBlock->dwPageSize);
      pvTemp = data;
      data = ((LPBYTE)pBlock->pvCrypt) + CRYPT_OFFSET;
    }

    dwPageSize = pBlock->dwCryptSize;
    CryptDecrypt(pBlock->hReadKey, 0, TRUE, 0, (LPBYTE)data, &dwPageSize);

    /* If the encryption algorithm required extra padding and we were forced to encrypt or
    ** decrypt a copy of the page data to a temp buffer, then write the contents of the temp
    ** buffer back to the page data minus any padding applied.
    */
    if (pBlock->dwCryptSize != pBlock->dwPageSize)
    {
      CopyMemory(pvTemp, data, pBlock->dwPageSize);
      data = pvTemp;
    }
    break;
  case 6: /* Encrypt a page for the main database file */
    if (!pBlock->hWriteKey) break;

    CopyMemory(((LPBYTE)pBlock->pvCrypt) + CRYPT_OFFSET, data, pBlock->dwPageSize);
    data = ((LPBYTE)pBlock->pvCrypt) + CRYPT_OFFSET;

    dwPageSize = pBlock->dwPageSize;
    CryptEncrypt(pBlock->hWriteKey, 0, TRUE, 0, ((LPBYTE)pBlock->pvCrypt) + CRYPT_OFFSET, &dwPageSize, pBlock->dwCryptSize);
    break;
  case 7: /* Encrypt a page for the journal file */
    /* Under normal circumstances, the readkey is the same as the writekey.  However,
    when the database is being rekeyed, the readkey is not the same as the writekey.
    The rollback journal must be written using the original key for the
    database file because it is, by nature, a rollback journal.
    Therefore, for case 7, when the rollback is being written, always encrypt using
    the database's readkey, which is guaranteed to be the same key that was used to
    read the original data.
    */
    if (!pBlock->hReadKey) break;

    CopyMemory(((LPBYTE)pBlock->pvCrypt) + CRYPT_OFFSET, data, pBlock->dwPageSize);
    data = ((LPBYTE)pBlock->pvCrypt) + CRYPT_OFFSET;

    dwPageSize = pBlock->dwPageSize;
    CryptEncrypt(pBlock->hReadKey, 0, TRUE, 0, ((LPBYTE)pBlock->pvCrypt) + CRYPT_OFFSET, &dwPageSize, pBlock->dwCryptSize);
    break;
  }

  return data;
}

/* Derive an encryption key from a user-supplied buffer */
static HCRYPTKEY DeriveKey(const void *pKey, int nKey)
{
  HCRYPTHASH hHash = 0;
  HCRYPTKEY  hKey;

  if (!pKey || !nKey) return 0;

  if (!InitializeProvider())
  {
    return MAXDWORD;
  }

  {
    MUTEX_LOGIC( systemdata_sqlite3_mutex *pMaster = systemdata_sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER); )
    systemdata_sqlite3_mutex_enter(pMaster);

    if (CryptCreateHash(g_hProvider, CALG_SHA1, 0, 0, &hHash))
    {
      if (CryptHashData(hHash, (LPBYTE)pKey, nKey, 0))
      {
        CryptDeriveKey(g_hProvider, CALG_RC4, hHash, 0, &hKey);
      }
      CryptDestroyHash(hHash);
    }

    systemdata_sqlite3_mutex_leave(pMaster);
  }

  return hKey;
}

/* Called by sqlite and systemdata_sqlite3_key_interop to attach a key to a database. */
int systemdata_sqlite3CodecAttach(systemdata_sqlite3 *db, int nDb, const void *pKey, int nKeyLen)
{
  int rc = SQLITE_ERROR;
  HCRYPTKEY hKey = 0;

  /* No key specified, could mean either use the main db's encryption or no encryption */
  if (!pKey || !nKeyLen)
  {
    if (!nDb)
    {
      return SQLITE_OK; /* Main database, no key specified so not encrypted */
    }
    else /* Attached database, use the main database's key */
    {
      /* Get the encryption block for the main database and attempt to duplicate the key
      ** for use by the attached database
      */
      Pager *p = systemdata_sqlite3BtreePager(db->aDb[0].pBt);
      LPCRYPTBLOCK pBlock = (LPCRYPTBLOCK)systemdata_sqlite3pager_get_codecarg(p);

      if (!pBlock) return SQLITE_OK; /* Main database is not encrypted so neither will be any attached database */
      if (!pBlock->hReadKey) return SQLITE_OK; /* Not encrypted */

      if (!CryptDuplicateKey(pBlock->hReadKey, NULL, 0, &hKey))
        return rc; /* Unable to duplicate the key */
    }
  }
  else /* User-supplied passphrase, so create a cryptographic key out of it */
  {
    hKey = DeriveKey(pKey, nKeyLen);
    if (hKey == MAXDWORD)
    {
#if SQLITE_VERSION_NUMBER >= 3008007
      systemdata_sqlite3ErrorWithMsg(db, rc, SQLITECRYPTERROR_PROVIDER);
#else
      systemdata_sqlite3Error(db, rc, SQLITECRYPTERROR_PROVIDER);
#endif
      return rc;
    }
  }

  /* Create a new encryption block and assign the codec to the new attached database */
  if (hKey)
  {
    Pager *p = systemdata_sqlite3BtreePager(db->aDb[nDb].pBt);
    LPCRYPTBLOCK pBlock = CreateCryptBlock(hKey, p, -1, NULL);
    if (!pBlock) return SQLITE_NOMEM;

    systemdata_sqlite3PagerSetCodec(p, systemdata_sqlite3Codec, systemdata_sqlite3CodecSizeChange, systemdata_sqlite3CodecFree, pBlock);

    rc = SQLITE_OK;
  }
  return rc;
}

/* Once a password has been supplied and a key created, we don't keep the
** original password for security purposes.  Therefore return NULL.
*/
void systemdata_sqlite3CodecGetKey(systemdata_sqlite3 *db, int nDb, void **ppKey, int *pnKeyLen)
{
  Btree *pbt = db->aDb[0].pBt;
  Pager *p = systemdata_sqlite3BtreePager(pbt);
  LPCRYPTBLOCK pBlock = (LPCRYPTBLOCK)systemdata_sqlite3pager_get_codecarg(p);

  if (ppKey) *ppKey = 0;
  if (pnKeyLen) *pnKeyLen = pBlock ? 1: 0;
}

SQLITE_API void systemdata_sqlite3_activate_see(const char *info)
{
}

/* We do not attach this key to the temp store, only the main database. */
SQLITE_API int systemdata_sqlite3_key_v2(systemdata_sqlite3 *db, const char *zDbName, const void *pKey, int nKey)
{
  return systemdata_sqlite3CodecAttach(db, 0, pKey, nKey);
}

SQLITE_API int systemdata_sqlite3_key(systemdata_sqlite3 *db, const void *pKey, int nKey)
{
  return systemdata_sqlite3_key_v2(db, 0, pKey, nKey);
}

SQLITE_API int systemdata_sqlite3_rekey(systemdata_sqlite3 *db, const void *pKey, int nKey)
{
  return systemdata_sqlite3_rekey_v2(db, 0, pKey, nKey);
}

/* Changes the encryption key for an existing database. */
SQLITE_API int systemdata_sqlite3_rekey_v2(systemdata_sqlite3 *db, const char *zDbName, const void *pKey, int nKey)
{
  Btree *pbt = db->aDb[0].pBt;
  Pager *p = systemdata_sqlite3BtreePager(pbt);
  LPCRYPTBLOCK pBlock = (LPCRYPTBLOCK)systemdata_sqlite3pager_get_codecarg(p);
  HCRYPTKEY hKey = DeriveKey(pKey, nKey);
  int rc = SQLITE_ERROR;

  if (hKey == MAXDWORD)
  {
#if SQLITE_VERSION_NUMBER >= 3008007
    systemdata_sqlite3ErrorWithMsg(db, rc, SQLITECRYPTERROR_PROVIDER);
#else
    systemdata_sqlite3Error(db, rc, SQLITECRYPTERROR_PROVIDER);
#endif
    return rc;
  }

  if (!pBlock && !hKey) return SQLITE_OK; /* Wasn't encrypted to begin with */

  /* To rekey a database, we change the writekey for the pager.  The readkey remains
  ** the same
  */
  if (!pBlock) /* Encrypt an unencrypted database */
  {
    pBlock = CreateCryptBlock(hKey, p, -1, NULL);
    if (!pBlock)
      return SQLITE_NOMEM;

    pBlock->hReadKey = 0; /* Original database is not encrypted */
    systemdata_sqlite3PagerSetCodec(systemdata_sqlite3BtreePager(pbt), systemdata_sqlite3Codec, systemdata_sqlite3CodecSizeChange, systemdata_sqlite3CodecFree, pBlock);
  }
  else /* Change the writekey for an already-encrypted database */
  {
    pBlock->hWriteKey = hKey;
  }

  systemdata_sqlite3_mutex_enter(db->mutex);

  /* Start a transaction */
  rc = systemdata_sqlite3BtreeBeginTrans(pbt, 1);

  if (!rc)
  {
    /* Rewrite all the pages in the database using the new encryption key */
    Pgno nPage;
    Pgno nSkip = PAGER_MJ_PGNO(p);
    DbPage *pPage;
    Pgno n;
    int count;

    systemdata_sqlite3PagerPagecount(p, &count);
    nPage = (Pgno)count;

    for(n = 1; n <= nPage; n ++)
    {
      if (n == nSkip) continue;
      rc = INTEROP_CODEC_GET_PAGER(p, n, &pPage);
      if(!rc)
      {
        rc = systemdata_sqlite3PagerWrite(pPage);
        systemdata_sqlite3PagerUnref(pPage);
      }
    }
  }

  /* If we succeeded, try and commit the transaction */
  if (!rc)
  {
    rc = systemdata_sqlite3BtreeCommit(pbt);
  }

  // If we failed, rollback */
  if (rc)
  {
#if SQLITE_VERSION_NUMBER >= 3008007
    systemdata_sqlite3BtreeRollback(pbt, SQLITE_OK, 0);
#else
    systemdata_sqlite3BtreeRollback(pbt, SQLITE_OK);
#endif
  }

  /* If we succeeded, destroy any previous read key this database used
  ** and make the readkey equal to the writekey
  */
  if (!rc)
  {
    if (pBlock->hReadKey)
    {
      CryptDestroyKey(pBlock->hReadKey);
    }
    pBlock->hReadKey = pBlock->hWriteKey;
  }
  /* We failed.  Destroy the new writekey (if there was one) and revert it back to
  ** the original readkey
  */
  else
  {
    if (pBlock->hWriteKey)
    {
      CryptDestroyKey(pBlock->hWriteKey);
    }
    pBlock->hWriteKey = pBlock->hReadKey;
  }

  /* If the readkey and writekey are both empty, there's no need for a codec on this
  ** pager anymore.  Destroy the crypt block and remove the codec from the pager.
  */
  if (!pBlock->hReadKey && !pBlock->hWriteKey)
  {
    systemdata_sqlite3PagerSetCodec(p, NULL, NULL, NULL, NULL);
  }

  systemdata_sqlite3_mutex_leave(db->mutex);

  return rc;
}

#endif /* SQLITE_HAS_CODEC */
#endif /* SQLITE_OMIT_DISKIO */