fix(fips): add cipher config API, key zeroization, and provider leak guard
- Add fips_configure_cipher() to pin FIPS-approved SQLCipher PRAGMAs (HMAC-SHA512, PBKDF2-HMAC-SHA512, 256k iterations, 4096-byte pages) - Call fips_configure_cipher() after sqlite3_key() in JNI and sample app - Copy key material to OPENSSL_malloc buffer and zeroize with OPENSSL_cleanse after use (SP 800-132 §5.3) - Fix provider leak: unload FIPS provider on EVP_set_default_properties failure to prevent silent FIPS bypass - Delete stale DB before round-trip compliance test - Add .codegraph/ and .grepai/ to .gitignore - Document fips_configure_cipher API and key handling in CLAUDE.md
This commit is contained in:
+4
-2
@@ -114,8 +114,10 @@ samples/android/local.properties
|
||||
.mise.local.toml
|
||||
|
||||
# grepai
|
||||
.grepai/*.gob
|
||||
.grepai/*.lock
|
||||
.grepai/
|
||||
|
||||
# codegraph
|
||||
.codegraph/
|
||||
|
||||
# Secrets & Config
|
||||
.env
|
||||
|
||||
@@ -92,6 +92,10 @@ Bumping `OPENSSL_VERSION` requires updating `OPENSSL_URL_HASH` and re-running FI
|
||||
|
||||
`include/fips_init.h` + `src/fips_init.c` -- portable C API for FIPS provider bootstrap at app startup. Uses programmatic APIs (`OSSL_PROVIDER_set_default_search_path`, `OSSL_LIB_CTX_load_config`, `OSSL_PROVIDER_load`) to bypass Android's `AT_SECURE` which blocks all `OPENSSL_*` env vars in app processes.
|
||||
|
||||
`fips_configure_cipher(sqlite3 *db)` -- must be called immediately after `sqlite3_key()` and before any I/O. Pins FIPS-approved cipher PRAGMAs: AES-256-CBC, HMAC-SHA512, PBKDF2-HMAC-SHA512 with 256k iterations, 4096-byte pages. These match SQLCipher 4.x defaults but are set explicitly for FIPS audit trail.
|
||||
|
||||
Key material handling in JNI (`src/fips_db_jni.c`): key is copied into an `OPENSSL_malloc` buffer and zeroized with `OPENSSL_cleanse` after use per SP 800-132 §5.3.
|
||||
|
||||
Platform-specific wrappers:
|
||||
- `src/fips_init_android.c` -- sets search path + loads config with absolute paths, then loads provider
|
||||
- `src/fips_init_ios.c` -- loads provider from bundle resource path
|
||||
|
||||
@@ -35,6 +35,11 @@ fips_init_status_t fips_init(const char *module_dir, const char *conf_path);
|
||||
int fips_self_test_rerun(void);
|
||||
int fips_provider_is_active(void);
|
||||
|
||||
// Configure a freshly keyed SQLCipher connection for FIPS-compliant operation.
|
||||
// Call immediately after sqlite3_key(). Returns SQLITE_OK (0) on success.
|
||||
typedef struct sqlite3 sqlite3;
|
||||
int fips_configure_cipher(sqlite3 *db);
|
||||
|
||||
#ifdef __ANDROID__
|
||||
// Android convenience wrapper.
|
||||
// files_dir: Context.getFilesDir() — expects fips/openssl.cnf underneath
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <sqlite3.h>
|
||||
#include "fips_init.h"
|
||||
#include <openssl/provider.h>
|
||||
#include <openssl/crypto.h>
|
||||
@@ -117,6 +118,8 @@ fips_init_status_t fips_init(const char *module_dir, const char *conf_path) {
|
||||
// are restricted to FIPS-approved implementations only.
|
||||
if (EVP_set_default_properties(NULL, "fips=yes") != 1) {
|
||||
LOGE("Failed to set fips=yes default properties\n");
|
||||
OSSL_PROVIDER_unload(g_fips_provider);
|
||||
g_fips_provider = NULL;
|
||||
return FIPS_INIT_ERR_PROPERTY_SET;
|
||||
}
|
||||
|
||||
@@ -124,6 +127,31 @@ fips_init_status_t fips_init(const char *module_dir, const char *conf_path) {
|
||||
return FIPS_INIT_OK;
|
||||
}
|
||||
|
||||
int fips_configure_cipher(sqlite3 *db) {
|
||||
static const char *pragmas[] = {
|
||||
"PRAGMA cipher_compatibility = 4",
|
||||
"PRAGMA cipher_plaintext_header_size = 0",
|
||||
"PRAGMA cipher_use_hmac = ON",
|
||||
"PRAGMA cipher_hmac_algorithm = HMAC_SHA512",
|
||||
"PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA512",
|
||||
"PRAGMA cipher_default_kdf_iter = 256000",
|
||||
"PRAGMA cipher_page_size = 4096",
|
||||
NULL,
|
||||
};
|
||||
for (const char **p = pragmas; *p; ++p) {
|
||||
char *err = NULL;
|
||||
int rc = sqlite3_exec(db, *p, NULL, NULL, &err);
|
||||
if (rc != SQLITE_OK) {
|
||||
LOGE("FIPS cipher config failed [%s]: %s\n", *p,
|
||||
err ? err : sqlite3_errmsg(db));
|
||||
if (err) sqlite3_free(err);
|
||||
return rc;
|
||||
}
|
||||
if (err) sqlite3_free(err);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
int fips_self_test_rerun(void) {
|
||||
if (!g_fips_provider) return 0;
|
||||
return OSSL_PROVIDER_self_test(g_fips_provider) == 1 ? 1 : 0;
|
||||
|
||||
@@ -35,6 +35,11 @@ fips_init_status_t fips_init(const char *module_dir, const char *conf_path);
|
||||
int fips_self_test_rerun(void);
|
||||
int fips_provider_is_active(void);
|
||||
|
||||
// Configure a freshly keyed SQLCipher connection for FIPS-compliant operation.
|
||||
// Call immediately after sqlite3_key(). Returns SQLITE_OK (0) on success.
|
||||
typedef struct sqlite3 sqlite3;
|
||||
int fips_configure_cipher(sqlite3 *db);
|
||||
|
||||
#ifdef __ANDROID__
|
||||
// Android convenience wrapper.
|
||||
// files_dir: Context.getFilesDir() — expects fips/openssl.cnf underneath
|
||||
|
||||
@@ -84,6 +84,12 @@ static int exec_sql(sqlite3 *db, const char *sql) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int key_and_configure(sqlite3 *db, const char *key, int key_len) {
|
||||
int rc = sqlite3_key(db, key, key_len);
|
||||
if (rc != SQLITE_OK) return rc;
|
||||
return fips_configure_cipher(db);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_example_fipsdemo_FipsNative_dbRoundTrip(JNIEnv *env, jclass cls,
|
||||
jstring jpath, jstring jkey) {
|
||||
@@ -98,8 +104,8 @@ Java_com_example_fipsdemo_FipsNative_dbRoundTrip(JNIEnv *env, jclass cls,
|
||||
snprintf(result, sizeof(result), "FAIL: open: %s", sqlite3_errmsg(db));
|
||||
goto out;
|
||||
}
|
||||
if (sqlite3_key(db, key, (int)strlen(key)) != SQLITE_OK) {
|
||||
snprintf(result, sizeof(result), "FAIL: key");
|
||||
if (key_and_configure(db, key, (int)strlen(key)) != SQLITE_OK) {
|
||||
snprintf(result, sizeof(result), "FAIL: key/configure: %s", sqlite3_errmsg(db));
|
||||
goto out;
|
||||
}
|
||||
if (exec_sql(db, "CREATE TABLE IF NOT EXISTS demo(id INTEGER PRIMARY KEY, msg TEXT, ts REAL)") != SQLITE_OK) {
|
||||
@@ -140,13 +146,13 @@ Java_com_example_fipsdemo_FipsNative_dbRekey(JNIEnv *env, jclass cls,
|
||||
jboolean ok = JNI_FALSE;
|
||||
|
||||
if (sqlite3_open(path, &db) != SQLITE_OK) goto out;
|
||||
if (sqlite3_key(db, oldK, (int)strlen(oldK)) != SQLITE_OK) goto out;
|
||||
if (key_and_configure(db, oldK, (int)strlen(oldK)) != SQLITE_OK) goto out;
|
||||
if (exec_sql(db, "SELECT count(*) FROM sqlite_master") != SQLITE_OK) goto out;
|
||||
if (sqlite3_rekey(db, newK, (int)strlen(newK)) != SQLITE_OK) goto out;
|
||||
sqlite3_close(db); db = NULL;
|
||||
|
||||
if (sqlite3_open(path, &db) != SQLITE_OK) goto out;
|
||||
if (sqlite3_key(db, newK, (int)strlen(newK)) != SQLITE_OK) goto out;
|
||||
if (key_and_configure(db, newK, (int)strlen(newK)) != SQLITE_OK) goto out;
|
||||
if (exec_sql(db, "SELECT count(*) FROM sqlite_master") == SQLITE_OK) ok = JNI_TRUE;
|
||||
|
||||
out:
|
||||
@@ -168,7 +174,7 @@ Java_com_example_fipsdemo_FipsNative_dbWrongKeyRejected(JNIEnv *env, jclass cls,
|
||||
jboolean rejected = JNI_FALSE;
|
||||
|
||||
if (sqlite3_open(path, &db) == SQLITE_OK) {
|
||||
if (sqlite3_key(db, bad, (int)strlen(bad)) == SQLITE_OK) {
|
||||
if (key_and_configure(db, bad, (int)strlen(bad)) == SQLITE_OK) {
|
||||
int rc = sqlite3_exec(db, "SELECT count(*) FROM sqlite_master",
|
||||
NULL, NULL, NULL);
|
||||
rejected = (rc != SQLITE_OK) ? JNI_TRUE : JNI_FALSE;
|
||||
|
||||
@@ -29,6 +29,7 @@ fun runComplianceSuite(filesDir: String): List<ComplianceCheck> {
|
||||
v.startsWith("OpenSSL 3.0.") to v
|
||||
},
|
||||
check("DB Write/Read", "SQLCipher encrypted round-trip via FIPS crypto") {
|
||||
java.io.File(dbPath).delete()
|
||||
val result = FipsNative.dbRoundTrip(dbPath, key)
|
||||
result.startsWith("OK") to result
|
||||
},
|
||||
|
||||
+30
-11
@@ -5,9 +5,11 @@
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
#include <openssl/crypto.h>
|
||||
#include <sqlite3.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "fips_init.h"
|
||||
|
||||
#define TAG "FipsDatabase"
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
|
||||
@@ -17,32 +19,49 @@ Java_com_fips_sqlcipher_FipsDatabase_nativeOpen(JNIEnv *env, jclass cls,
|
||||
jstring jpath, jstring jkey) {
|
||||
(void)cls;
|
||||
const char *path = (*env)->GetStringUTFChars(env, jpath, NULL);
|
||||
const char *key = (*env)->GetStringUTFChars(env, jkey, NULL);
|
||||
const char *jkey_str = (*env)->GetStringUTFChars(env, jkey, NULL);
|
||||
sqlite3 *db = NULL;
|
||||
jlong result = 0;
|
||||
|
||||
int key_len = (int)strlen(jkey_str);
|
||||
char *key = (char *)OPENSSL_malloc(key_len + 1);
|
||||
if (!key) {
|
||||
(*env)->ReleaseStringUTFChars(env, jpath, path);
|
||||
(*env)->ReleaseStringUTFChars(env, jkey, jkey_str);
|
||||
return 0;
|
||||
}
|
||||
memcpy(key, jkey_str, key_len + 1);
|
||||
(*env)->ReleaseStringUTFChars(env, jkey, jkey_str);
|
||||
|
||||
int rc = sqlite3_open(path, &db);
|
||||
if (rc != SQLITE_OK) {
|
||||
LOGE("sqlite3_open(%s) failed: %s", path, sqlite3_errmsg(db));
|
||||
if (db) sqlite3_close(db);
|
||||
(*env)->ReleaseStringUTFChars(env, jpath, path);
|
||||
(*env)->ReleaseStringUTFChars(env, jkey, key);
|
||||
return 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (key && key[0]) {
|
||||
rc = sqlite3_key(db, key, (int)strlen(key));
|
||||
if (key[0]) {
|
||||
rc = sqlite3_key(db, key, key_len);
|
||||
if (rc != SQLITE_OK) {
|
||||
LOGE("sqlite3_key failed: %s", sqlite3_errmsg(db));
|
||||
sqlite3_close(db);
|
||||
(*env)->ReleaseStringUTFChars(env, jpath, path);
|
||||
(*env)->ReleaseStringUTFChars(env, jkey, key);
|
||||
return 0;
|
||||
goto out;
|
||||
}
|
||||
rc = fips_configure_cipher(db);
|
||||
if (rc != SQLITE_OK) {
|
||||
LOGE("fips_configure_cipher failed: %s", sqlite3_errmsg(db));
|
||||
sqlite3_close(db);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
result = (jlong)(intptr_t)db;
|
||||
|
||||
out:
|
||||
OPENSSL_cleanse(key, key_len);
|
||||
OPENSSL_free(key);
|
||||
(*env)->ReleaseStringUTFChars(env, jpath, path);
|
||||
(*env)->ReleaseStringUTFChars(env, jkey, key);
|
||||
return (jlong)(intptr_t)db;
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <sqlite3.h>
|
||||
#include "fips_init.h"
|
||||
#include <openssl/provider.h>
|
||||
#include <openssl/crypto.h>
|
||||
@@ -117,6 +118,8 @@ fips_init_status_t fips_init(const char *module_dir, const char *conf_path) {
|
||||
// are restricted to FIPS-approved implementations only.
|
||||
if (EVP_set_default_properties(NULL, "fips=yes") != 1) {
|
||||
LOGE("Failed to set fips=yes default properties\n");
|
||||
OSSL_PROVIDER_unload(g_fips_provider);
|
||||
g_fips_provider = NULL;
|
||||
return FIPS_INIT_ERR_PROPERTY_SET;
|
||||
}
|
||||
|
||||
@@ -124,6 +127,31 @@ fips_init_status_t fips_init(const char *module_dir, const char *conf_path) {
|
||||
return FIPS_INIT_OK;
|
||||
}
|
||||
|
||||
int fips_configure_cipher(sqlite3 *db) {
|
||||
static const char *pragmas[] = {
|
||||
"PRAGMA cipher_compatibility = 4",
|
||||
"PRAGMA cipher_plaintext_header_size = 0",
|
||||
"PRAGMA cipher_use_hmac = ON",
|
||||
"PRAGMA cipher_hmac_algorithm = HMAC_SHA512",
|
||||
"PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA512",
|
||||
"PRAGMA cipher_default_kdf_iter = 256000",
|
||||
"PRAGMA cipher_page_size = 4096",
|
||||
NULL,
|
||||
};
|
||||
for (const char **p = pragmas; *p; ++p) {
|
||||
char *err = NULL;
|
||||
int rc = sqlite3_exec(db, *p, NULL, NULL, &err);
|
||||
if (rc != SQLITE_OK) {
|
||||
LOGE("FIPS cipher config failed [%s]: %s\n", *p,
|
||||
err ? err : sqlite3_errmsg(db));
|
||||
if (err) sqlite3_free(err);
|
||||
return rc;
|
||||
}
|
||||
if (err) sqlite3_free(err);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
int fips_self_test_rerun(void) {
|
||||
if (!g_fips_provider) return 0;
|
||||
return OSSL_PROVIDER_self_test(g_fips_provider) == 1 ? 1 : 0;
|
||||
|
||||
Reference in New Issue
Block a user