All checks were successful
CI Code / Check spelling (pull_request) Successful in 15s
CI Code / Check coding style (pull_request) Successful in 26s
CI Code / Linux (debian) (pull_request) Successful in 5m3s
CI Code / Linux (arch) (pull_request) Successful in 6m25s
CI Code / Linux (ubuntu) (pull_request) Successful in 8m1s
CI Code / Code Coverage (pull_request) Successful in 9m27s
T03 — keep secrets out of profanity.log: - new redact_secrets() helper (common.c) masks the content of SASL auth/response/challenge/success and <password> elements; applied in the libstrophe logger (_xmpp_file_logger) and the stderr-to-log bridge (REQ-DAR-03, REQ-LOG-06) - _add_to_db() no longer logs decrypted message bodies or the full INSERT statement (REQ-DAR-03) T07 — owner-only permissions on key material and history (REQ-AUTH-01): - chmod 0600 on chatlog.db after open (journal/WAL files inherit) - chmod 0600 on OTR keys.txt and fingerprints.txt after every write - parent dirs are already 0700 (create_dir); this is defense in depth T08 — integrity before trust: - PRAGMA quick_check(1) gates every chatlog.db open; on failure the DB stays closed, the user is warned once in the console and the session continues without history (REQ-RES-02) - OMEMO aesgcm downloads decrypt into a 0600 tempfile next to the target and rename it into place only after the GCM tag verifies; the open-command hook no longer runs on failed decryption (REQ-CRY-06) Tests: redact_secrets unit tests; AES-256-GCM roundtrip and tampered-tag/ciphertext unit tests (crypto.c now linked into unittests under BUILD_OMEMO); functional test planting a corrupt chatlog.db and asserting graceful degradation. Closes #146
153 lines
4.1 KiB
C
153 lines
4.1 KiB
C
/*
|
|
* test_omemo_crypto.c
|
|
*
|
|
* Unit tests for the OMEMO AES-256-GCM file crypto (src/omemo/crypto.c).
|
|
* The decrypt direction streams plaintext before the tag is checked, so
|
|
* callers rely on the returned error code to discard unverified output —
|
|
* these tests pin that contract (issue #146, REQ-CRY-06).
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <glib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "prof_cmocka.h"
|
|
|
|
#ifdef HAVE_OMEMO
|
|
|
|
#include "omemo/omemo.h"
|
|
#include "omemo/crypto.h"
|
|
|
|
#define TAG_LENGTH 16
|
|
|
|
static const unsigned char PLAINTEXT[] = "at-rest integrity check payload: 0123456789abcdef";
|
|
|
|
// gcrypt secure memory must be set up exactly once per process
|
|
static int
|
|
_crypto_init_once(void)
|
|
{
|
|
static gboolean done = FALSE;
|
|
static int rc = 0;
|
|
if (!done) {
|
|
rc = omemo_crypto_init();
|
|
done = TRUE;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static off_t
|
|
_file_size(FILE* fh)
|
|
{
|
|
fseeko(fh, 0, SEEK_END);
|
|
off_t size = ftello(fh);
|
|
rewind(fh);
|
|
return size;
|
|
}
|
|
|
|
// encrypt PLAINTEXT with a fixed key/nonce into a fresh tmpfile
|
|
static FILE*
|
|
_encrypted_tmpfile(unsigned char* key, unsigned char* nonce)
|
|
{
|
|
memset(key, 0x42, OMEMO_AESGCM_KEY_LENGTH);
|
|
memset(nonce, 0x24, OMEMO_AESGCM_NONCE_LENGTH);
|
|
|
|
FILE* plain = tmpfile();
|
|
FILE* cipher = tmpfile();
|
|
assert_non_null(plain);
|
|
assert_non_null(cipher);
|
|
|
|
assert_int_equal(sizeof(PLAINTEXT), fwrite(PLAINTEXT, 1, sizeof(PLAINTEXT), plain));
|
|
rewind(plain);
|
|
|
|
assert_int_equal(GPG_ERR_NO_ERROR,
|
|
aes256gcm_crypt_file(plain, cipher, (off_t)sizeof(PLAINTEXT), key, nonce, TRUE));
|
|
fclose(plain);
|
|
rewind(cipher);
|
|
return cipher;
|
|
}
|
|
|
|
// corrupt one byte at offset (negative counts from the end), return reopened stream
|
|
static FILE*
|
|
_flip_byte(FILE* cipher, long offset)
|
|
{
|
|
off_t size = _file_size(cipher);
|
|
unsigned char* buf = g_malloc(size);
|
|
assert_int_equal(size, fread(buf, 1, size, cipher));
|
|
fclose(cipher);
|
|
|
|
long pos = offset >= 0 ? offset : (long)size + offset;
|
|
buf[pos] ^= 0xFF;
|
|
|
|
FILE* tampered = tmpfile();
|
|
assert_non_null(tampered);
|
|
assert_int_equal(size, fwrite(buf, 1, size, tampered));
|
|
rewind(tampered);
|
|
g_free(buf);
|
|
return tampered;
|
|
}
|
|
|
|
void
|
|
aes256gcm_crypt_file__roundtrip_succeeds(void** state)
|
|
{
|
|
assert_int_equal(0, _crypto_init_once());
|
|
|
|
unsigned char key[OMEMO_AESGCM_KEY_LENGTH];
|
|
unsigned char nonce[OMEMO_AESGCM_NONCE_LENGTH];
|
|
FILE* cipher = _encrypted_tmpfile(key, nonce);
|
|
|
|
off_t cipher_size = _file_size(cipher);
|
|
assert_int_equal((off_t)sizeof(PLAINTEXT) + TAG_LENGTH, cipher_size);
|
|
|
|
FILE* decrypted = tmpfile();
|
|
assert_non_null(decrypted);
|
|
assert_int_equal(GPG_ERR_NO_ERROR,
|
|
aes256gcm_crypt_file(cipher, decrypted, cipher_size, key, nonce, FALSE));
|
|
|
|
unsigned char readback[sizeof(PLAINTEXT)];
|
|
rewind(decrypted);
|
|
assert_int_equal(sizeof(PLAINTEXT), fread(readback, 1, sizeof(readback), decrypted));
|
|
assert_memory_equal(PLAINTEXT, readback, sizeof(PLAINTEXT));
|
|
|
|
fclose(cipher);
|
|
fclose(decrypted);
|
|
}
|
|
|
|
void
|
|
aes256gcm_crypt_file__rejects_tampered_tag(void** state)
|
|
{
|
|
assert_int_equal(0, _crypto_init_once());
|
|
|
|
unsigned char key[OMEMO_AESGCM_KEY_LENGTH];
|
|
unsigned char nonce[OMEMO_AESGCM_NONCE_LENGTH];
|
|
FILE* cipher = _flip_byte(_encrypted_tmpfile(key, nonce), -1); // last tag byte
|
|
|
|
FILE* decrypted = tmpfile();
|
|
assert_non_null(decrypted);
|
|
gcry_error_t res = aes256gcm_crypt_file(cipher, decrypted, _file_size(cipher), key, nonce, FALSE);
|
|
assert_int_not_equal(GPG_ERR_NO_ERROR, res);
|
|
|
|
fclose(cipher);
|
|
fclose(decrypted);
|
|
}
|
|
|
|
void
|
|
aes256gcm_crypt_file__rejects_tampered_ciphertext(void** state)
|
|
{
|
|
assert_int_equal(0, _crypto_init_once());
|
|
|
|
unsigned char key[OMEMO_AESGCM_KEY_LENGTH];
|
|
unsigned char nonce[OMEMO_AESGCM_NONCE_LENGTH];
|
|
FILE* cipher = _flip_byte(_encrypted_tmpfile(key, nonce), 0); // first payload byte
|
|
|
|
FILE* decrypted = tmpfile();
|
|
assert_non_null(decrypted);
|
|
gcry_error_t res = aes256gcm_crypt_file(cipher, decrypted, _file_size(cipher), key, nonce, FALSE);
|
|
assert_int_not_equal(GPG_ERR_NO_ERROR, res);
|
|
|
|
fclose(cipher);
|
|
fclose(decrypted);
|
|
}
|
|
|
|
#endif
|