Browse Source

ENGINE-866 feature branch merge (squashed commit) of functionality to set the sticky bit for manually imported keys, to query for that bit in the trust database, and prevention of automatic reset of sticky keys by key reset when devices leave a device group.

Squashed commit of the following:

commit c64d850dc4
Author: Krista Bennett <krista@pep.foundation>
Date:   Fri Feb 26 15:29:32 2021 +0100

    ENGINE-866: doc'd bit getter function

commit ad725b5b7c
Author: Krista Bennett <krista@pep.foundation>
Date:   Fri Feb 26 15:23:49 2021 +0100

    ENGINE-866: Key reset tested on mixed sticky and not sticky keys and does what it should.

commit 0ffbdde7b5
Author: Krista Bennett <krista@pep.foundation>
Date:   Fri Feb 26 15:13:53 2021 +0100

    ENGINE-866: Add boolean for whether to set the sticky bit or not with set_own_imported_key. the adapter should filter this out for apps, I guess, according to Volker

commit 23fec59a9a
Author: Krista Bennett <krista@pep.foundation>
Date:   Fri Feb 26 14:53:19 2021 +0100

    ENGINE-866: Test and use the sticky bit

commit 562239fda8
Author: Krista Bennett <krista@pep.foundation>
Date:   Thu Feb 25 16:47:47 2021 +0100

    ENGINE-866: moved bit from key to trust, created set_own_imported_key to replace set_own_key FOR MAIL APPS (does NOT replace it for key reset, as the new function can generate a passphrase error, whereas set_own_key cannot), and did an initial test to ensure the setter/getter functions work on the DB.

commit 594133cfde
Author: Krista Bennett <krista@pep.foundation>
Date:   Wed Feb 24 11:16:21 2021 +0100

    Commented out the or'd identity.flags / pgp_keypair.flags in the sql code for the get_identity functions; we've never HAD a pgp_keypair flag before, so it never hurt before, but at this point, we're going to introduce them, and I don't want trouble. If fdik wants them or'd, fine, we'll have to change the values in the keyflags to be disjoint from the identity flags so they can coexist, but for now, they are out.

commit 99831445b3
Merge: 8ba53ece d1664cf5
Author: Krista Bennett <krista@pep.foundation>
Date:   Wed Feb 24 10:15:53 2021 +0100

    Merge branch 'master' into ENGINE-866

commit 8ba53ece06
Merge: 168e2cf9 c52f4d39
Author: Krista Bennett <krista@pep.foundation>
Date:   Mon Feb 22 20:06:08 2021 +0100

    Merged in engine_sql changes

commit 168e2cf957
Author: Krista Bennett <krista@pep.foundation>
Date:   Mon Feb 22 19:03:35 2021 +0100

    ENGINE-866: Added sticky bit in database for manually set keys
master
Krista Bennett 4 months ago
parent
commit
38ed5b5f25
15 changed files with 899 additions and 53 deletions
  1. +1
    -0
      .gitignore
  2. +63
    -2
      src/engine_sql.c
  3. +31
    -5
      src/engine_sql.h
  4. +7
    -0
      src/key_reset.c
  5. +88
    -1
      src/keymanagement.c
  6. +51
    -0
      src/keymanagement.h
  7. +0
    -18
      src/message_api.c
  8. +0
    -15
      src/message_api.h
  9. +17
    -0
      src/pEpEngine.c
  10. +20
    -4
      src/pEpEngine.h
  11. +4
    -0
      src/pEp_internal.h
  12. +1
    -0
      sync/cond_act_sync.yml2
  13. +234
    -8
      test/src/KeyResetMessageTest.cc
  14. +98
    -0
      test/src/StickyBitTest.cc
  15. +284
    -0
      test/test_mails/check_reset_all_own_grouped_sticky.eml

+ 1
- 0
.gitignore View File

@ -93,6 +93,7 @@ src/commit_hash.h
venv/
venv2/
cleangit.py
newgit
# ignore generated test data


+ 63
- 2
src/engine_sql.c View File

@ -679,8 +679,7 @@ static PEP_STATUS _create_core_tables(PEP_SESSION session) {
" created integer,\n"
" expires integer,\n"
" comment text,\n"
" flags integer default 0,\n"
" manually_set integer default 0\n"
" flags integer default 0\n"
");\n"
"create index if not exists pgp_keypair_expires on pgp_keypair (\n"
" expires\n"
@ -723,6 +722,7 @@ static PEP_STATUS _create_core_tables(PEP_SESSION session) {
" on delete cascade,\n"
" comm_type integer not null,\n"
" comment text,\n"
" sticky integer default 0,\n"
" primary key (user_id, pgp_keypair_fpr)\n"
");\n"
,
@ -1467,6 +1467,23 @@ static PEP_STATUS _upgrade_DB_to_ver_15(PEP_SESSION session) {
return _create_group_tables(session);
}
static PEP_STATUS _upgrade_DB_to_ver_16(PEP_SESSION session) {
int int_result = sqlite3_exec(
session->db,
"alter table trust\n"
" add column sticky integer default 0;\n",
NULL,
NULL,
NULL
);
assert(int_result == SQLITE_OK);
if (int_result != SQLITE_OK)
return PEP_UNKNOWN_DB_ERROR;
return PEP_STATUS_OK;
}
static PEP_STATUS _check_and_execute_upgrades(PEP_SESSION session, int version) {
PEP_STATUS status = PEP_STATUS_OK;
@ -1526,6 +1543,10 @@ static PEP_STATUS _check_and_execute_upgrades(PEP_SESSION session, int version)
if (status != PEP_STATUS_OK)
return status;
case 15:
status = _upgrade_DB_to_ver_16(session);
if (status != PEP_STATUS_OK)
return status;
case 16:
break;
default:
return PEP_ILLEGAL_VALUE;
@ -1927,7 +1948,22 @@ PEP_STATUS pEp_prepare_sql_stmts(PEP_SESSION session) {
if (int_result != SQLITE_OK)
return PEP_UNKNOWN_DB_ERROR;
int_result = sqlite3_prepare_v2(session->db, sql_set_pgp_keypair_flags,
(int)strlen(sql_set_pgp_keypair_flags), &session->set_pgp_keypair_flags,
NULL);
assert(int_result == SQLITE_OK);
if (int_result != SQLITE_OK)
return PEP_UNKNOWN_DB_ERROR;
int_result = sqlite3_prepare_v2(session->db, sql_unset_pgp_keypair_flags,
(int)strlen(sql_unset_pgp_keypair_flags), &session->unset_pgp_keypair_flags,
NULL);
assert(int_result == SQLITE_OK);
if (int_result != SQLITE_OK)
return PEP_UNKNOWN_DB_ERROR;
int_result = sqlite3_prepare_v2(session->db, sql_set_identity_entry,
(int)strlen(sql_set_identity_entry), &session->set_identity_entry, NULL);
assert(int_result == SQLITE_OK);
@ -2069,6 +2105,23 @@ PEP_STATUS pEp_prepare_sql_stmts(PEP_SESSION session) {
return PEP_UNKNOWN_DB_ERROR;
int_result = sqlite3_prepare_v2(session->db, sql_update_key_sticky_bit_for_user,
(int)strlen(sql_update_key_sticky_bit_for_user),
&session->update_key_sticky_bit_for_user, NULL);
assert(int_result == SQLITE_OK);
if (int_result != SQLITE_OK)
return PEP_UNKNOWN_DB_ERROR;
int_result = sqlite3_prepare_v2(session->db, sql_is_key_sticky_for_user,
(int)strlen(sql_is_key_sticky_for_user),
&session->is_key_sticky_for_user, NULL);
assert(int_result == SQLITE_OK);
if (int_result != SQLITE_OK)
return PEP_UNKNOWN_DB_ERROR;
int_result = sqlite3_prepare_v2(session->db, sql_mark_as_compromised,
(int)strlen(sql_mark_as_compromised), &session->mark_compromised,
NULL);
@ -2503,6 +2556,10 @@ PEP_STATUS pEp_finalize_sql_stmts(PEP_SESSION session) {
sqlite3_finalize(session->get_trust_by_userid);
if (session->least_trust)
sqlite3_finalize(session->least_trust);
if (session->update_key_sticky_bit_for_user)
sqlite3_finalize(session->update_key_sticky_bit_for_user);
if (session->is_key_sticky_for_user)
sqlite3_finalize(session->is_key_sticky_for_user);
if (session->mark_compromised)
sqlite3_finalize(session->mark_compromised);
if (session->crashdump)
@ -2595,6 +2652,10 @@ PEP_STATUS pEp_finalize_sql_stmts(PEP_SESSION session) {
sqlite3_finalize(session->is_invited_group_member);
if (session->is_group_active)
sqlite3_finalize(session->is_group_active);
if (session->set_pgp_keypair_flags)
sqlite3_finalize(session->set_pgp_keypair_flags);
if (session->unset_pgp_keypair_flags)
sqlite3_finalize(session->unset_pgp_keypair_flags);
// retrieve_own_membership_info_for_group_and_ident
// if (session->group_invite_exists)
// sqlite3_finalize(session->group_invite_exists);


+ 31
- 5
src/engine_sql.h View File

@ -3,7 +3,7 @@
#include "pEp_internal.h"
// increment this when patching DDL
#define _DDL_USER_VERSION "15"
#define _DDL_USER_VERSION "16"
PEP_STATUS init_databases(PEP_SESSION session);
PEP_STATUS pEp_sql_init(PEP_SESSION session);
@ -22,9 +22,12 @@ static const char *sql_trustword =
"and id = ?2 ;";
// FIXME?: problems if we don't have a key for the user - we get nothing
// Also: we've never used pgp_keypair.flags before now, but it seems to me that
// having combination of those flags is a road to ruin. Changing this for now.
static const char *sql_get_identity =
"select identity.main_key_id, username, comm_type, lang,"
" identity.flags | pgp_keypair.flags,"
" identity.flags,"
// " identity.flags | pgp_keypair.flags,"
" is_own, pEp_version_major, pEp_version_minor, enc_format"
" from identity"
" join person on id = identity.user_id"
@ -42,7 +45,8 @@ static const char *sql_get_identity =
static const char *sql_get_identities_by_main_key_id =
"select address, identity.user_id, username, comm_type, lang,"
" identity.flags | pgp_keypair.flags,"
" identity.flags,"
// " identity.flags | pgp_keypair.flags,"
" is_own, pEp_version_major, pEp_version_minor, enc_format"
" from identity"
" join person on id = identity.user_id"
@ -82,7 +86,8 @@ static const char *sql_get_identities_by_address =
static const char *sql_get_identities_by_userid =
"select address, identity.main_key_id, username, comm_type, lang,"
" identity.flags | pgp_keypair.flags,"
" identity.flags,"
// " identity.flags | pgp_keypair.flags,"
" is_own, pEp_version_major, pEp_version_minor, enc_format"
" from identity"
" join person on id = identity.user_id"
@ -176,7 +181,17 @@ static const char *sql_set_pgp_keypair =
"insert or ignore into pgp_keypair (fpr) "
"values (upper(replace(?1,' ',''))) ;";
static const char *sql_set_pgp_keypair_flags =
"update pgp_keypair set flags = "
" ((?1 & 65535) | (select flags from pgp_keypair "
" where fpr = (upper(replace(?2,' ',''))))) "
" where fpr = (upper(replace(?2,' ',''))) ;";
static const char *sql_unset_pgp_keypair_flags =
"update pgp_keypair set flags = "
" ( ~(?1 & 65535) & (select flags from pgp_keypair"
" where fpr = (upper(replace(?2,' ',''))))) "
" where fpr = (upper(replace(?2,' ',''))) ;";
static const char* sql_exists_identity_entry =
"select count(*) from identity "
@ -331,6 +346,14 @@ static const char *sql_least_trust =
" and comm_type != 0;"; // ignores PEP_ct_unknown
// returns PEP_ct_unknown only when no known trust is recorded
static const char *sql_update_key_sticky_bit_for_user =
"update trust set sticky = ?1 "
" where user_id = ?2 and pgp_keypair_fpr = upper(replace(?3,' ','')) ;";
static const char *sql_is_key_sticky_for_user =
"select sticky from trust "
" where user_id = ?1 and pgp_keypair_fpr = upper(replace(?2,' ','')) ; ";
static const char *sql_mark_as_compromised =
"update trust not indexed set comm_type = 15"
" where pgp_keypair_fpr = upper(replace(?1,' ','')) ;";
@ -385,7 +408,10 @@ static const char *sql_is_own_address =
static const char *sql_own_identities_retrieve =
"select address, identity.main_key_id, identity.user_id, username,"
" lang, identity.flags | pgp_keypair.flags, pEp_version_major, pEp_version_minor"
" lang,"
" identity.flags,"
// " identity.flags | pgp_keypair.flags,"
" pEp_version_major, pEp_version_minor"
" from identity"
" join person on id = identity.user_id"
" left join pgp_keypair on fpr = identity.main_key_id"


+ 7
- 0
src/key_reset.c View File

@ -1327,6 +1327,13 @@ DYNAMIC_API PEP_STATUS key_reset_own_grouped_keys(PEP_SESSION session) {
for (curr_key = keys; curr_key && curr_key->value; curr_key = curr_key->next) {
identity_list* key_idents = NULL;
const char* own_key = curr_key->value;
// If the sticky bit is set, ignore this beast
bool is_sticky = false;
status = get_key_sticky_bit_for_user(session, user_id, own_key, &is_sticky);
if (is_sticky)
continue;
status = get_identities_by_main_key_id(session, own_key, &key_idents);
if (status == PEP_CANNOT_FIND_IDENTITY) {


+ 88
- 1
src/keymanagement.c View File

@ -2091,6 +2091,58 @@ DYNAMIC_API PEP_STATUS own_keys_retrieve(PEP_SESSION session, stringlist_t **key
return _own_keys_retrieve(session, keylist, 0, true);
}
PEP_STATUS update_key_sticky_bit_for_user(PEP_SESSION session,
pEp_identity* ident,
const char* fpr,
bool sticky) {
if (!session || !ident || EMPTYSTR(ident->user_id) || EMPTYSTR(fpr))
return PEP_ILLEGAL_VALUE;
sqlite3_reset(session->update_key_sticky_bit_for_user);
sqlite3_bind_int(session->update_key_sticky_bit_for_user, 1, sticky);
sqlite3_bind_text(session->update_key_sticky_bit_for_user, 2, ident->user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->update_key_sticky_bit_for_user, 3, fpr, -1,
SQLITE_STATIC);
int result = sqlite3_step(session->update_key_sticky_bit_for_user);
sqlite3_reset(session->update_key_sticky_bit_for_user);
if (result != SQLITE_DONE) {
return PEP_CANNOT_SET_TRUST;
}
return PEP_STATUS_OK;
}
PEP_STATUS get_key_sticky_bit_for_user(PEP_SESSION session,
const char* user_id,
const char* fpr,
bool* is_sticky) {
PEP_STATUS status = PEP_STATUS_OK;
if (!session || !is_sticky || EMPTYSTR(user_id) || EMPTYSTR(fpr))
return PEP_ILLEGAL_VALUE;
sqlite3_reset(session->is_key_sticky_for_user);
sqlite3_bind_text(session->is_key_sticky_for_user, 1, user_id, -1,
SQLITE_STATIC);
sqlite3_bind_text(session->is_key_sticky_for_user, 2, fpr, -1,
SQLITE_STATIC);
int result = sqlite3_step(session->is_key_sticky_for_user);
switch (result) {
case SQLITE_ROW: {
*is_sticky = sqlite3_column_int(session->is_key_sticky_for_user, 0);
break;
}
default:
status = PEP_KEY_NOT_FOUND;
}
return status;
}
// Returns PASSPHRASE errors when necessary
DYNAMIC_API PEP_STATUS set_own_key(
PEP_SESSION session,
@ -2115,7 +2167,7 @@ DYNAMIC_API PEP_STATUS set_own_key(
// renew if needed, but do not generate
status = _myself(session, me, false, true, true, false);
// we do not need a valid key but dislike other errors
// Pass through invalidity errors, and reject other errors
if (status != PEP_STATUS_OK && status != PEP_GET_KEY_FAILED && status != PEP_KEY_UNSUITABLE)
return status;
status = PEP_STATUS_OK;
@ -2148,6 +2200,41 @@ DYNAMIC_API PEP_STATUS set_own_key(
return status;
}
// This differs from set_own_key because it can set a manually-imported bit in the trust DB
// and tests to see if the key will encrypt
DYNAMIC_API PEP_STATUS set_own_imported_key(
PEP_SESSION session,
pEp_identity* me,
const char* fpr,
bool sticky) {
PEP_STATUS status = PEP_STATUS_OK;
assert(session && me);
assert(!EMPTYSTR(fpr));
assert(!EMPTYSTR(me->address));
assert(!EMPTYSTR(me->user_id));
assert(!EMPTYSTR(me->username));
if (!session || !me || EMPTYSTR(fpr) || EMPTYSTR(me->address) ||
EMPTYSTR(me->user_id) || EMPTYSTR(me->username))
return PEP_ILLEGAL_VALUE;
// Last, but not least, be sure we can encrypt with it
status = probe_encrypt(session, fpr);
if (status)
return status;
status = set_own_key(session, me, fpr);
if (status != PEP_STATUS_OK)
return status;
status = update_key_sticky_bit_for_user(session, me, fpr, sticky);
return status;
}
PEP_STATUS contains_priv_key(PEP_SESSION session, const char *fpr,
bool *has_private) {


+ 51
- 0
src/keymanagement.h View File

@ -460,6 +460,40 @@ DYNAMIC_API PEP_STATUS set_own_key(
const char *fpr
);
/**
* <!-- set_own_imported_key() -->
*
* @brief Mark a key as an own default key, test to be sure the private key is
* present and can be used, and
*
* @param[in] session session to use
* @param[in,out] me own identity this key is used for
* @param[in] fpr fingerprint of the key to mark as own key
* @param[in] sticky boolean, true if we should set a sticky bit so
* it will not be automatically reset by sync and should
* win sync key elections if no other competing key
* for the same identity has its sticky bit set,
* false otherwise
*
* @warning the key has to be in the key ring already
* me->address, me->user_id and me->username must be set to valid data
* myself() is called by set_own_key() from within this call without key generation
* me->flags are ignored
* me->address must not be an alias
* me->fpr will be ignored and replaced by fpr, but
* caller MUST surrender ownership of the me->fpr reference, because
* it may be freed and replaced within the myself call. caller owns
* me->fpr memory again upon return.
* CAN GENERATE A PASSPHRASE REQUEST
*
*/
DYNAMIC_API PEP_STATUS set_own_imported_key(
PEP_SESSION session,
pEp_identity* me,
const char* fpr,
bool sticky
);
//
// clean_own_key_defaults()
//
@ -575,6 +609,23 @@ PEP_STATUS get_valid_pubkey(PEP_SESSION session,
bool* is_address_default,
bool check_blacklist);
/**
* <!-- get_key_sticky_bit_for_user() -->
*
* @brief Get value of sticky bit for this user and key
*
* @param[in] session PEP_SESSION
* @param[in] user_id user_id of key owner to get the sticky bit for
* @param[in] fpr fingerprint of user's key to consider
* @param[out] is_sticky (by reference) true if sticky bit is set for this user and fpr,
* else false
*
*/
PEP_STATUS get_key_sticky_bit_for_user(PEP_SESSION session,
const char* user_id,
const char* fpr,
bool* is_sticky);
#ifdef __cplusplus
}
#endif


+ 0
- 18
src/message_api.c View File

@ -2369,24 +2369,6 @@ static void update_encryption_format(identity_list* id_list, PEP_enc_format* enc
}
}
DYNAMIC_API PEP_STATUS probe_encrypt(PEP_SESSION session, const char *fpr)
{
assert(session);
if (!session || EMPTYSTR(fpr))
return PEP_ILLEGAL_VALUE;
stringlist_t *keylist = new_stringlist(fpr);
if (!keylist)
return PEP_OUT_OF_MEMORY;
char *ctext = NULL;
size_t csize = 0;
PEP_STATUS status = encrypt_and_sign(session, keylist, "pEp", 4, &ctext, &csize);
free(ctext);
return status;
}
/**
* @internal
*


+ 0
- 15
src/message_api.h View File

@ -694,21 +694,6 @@ PEP_STATUS try_encrypt_message(
PEP_encrypt_flags_t flags
);
/**
* <!-- probe_encrypt() -->
*
* @brief Test if passphrase for a key is working in current session
*
* @param[in] session session handle
* @param[in] fpr fingerprint of key to test
*
* @retval PEP_STATUS_OK in case passphrase works
* @retval error if not
*
*
*/
DYNAMIC_API PEP_STATUS probe_encrypt(PEP_SESSION session, const char *fpr);
#ifdef __cplusplus
}


+ 17
- 0
src/pEpEngine.c View File

@ -2661,6 +2661,23 @@ PEP_STATUS sign_only(PEP_SESSION session,
}
DYNAMIC_API PEP_STATUS probe_encrypt(PEP_SESSION session, const char *fpr)
{
assert(session);
if (!session || EMPTYSTR(fpr))
return PEP_ILLEGAL_VALUE;
stringlist_t *keylist = new_stringlist(fpr);
if (!keylist)
return PEP_OUT_OF_MEMORY;
char *ctext = NULL;
size_t csize = 0;
PEP_STATUS status = encrypt_and_sign(session, keylist, "pEp", 4, &ctext, &csize);
free(ctext);
return status;
}
DYNAMIC_API PEP_STATUS verify_text(


+ 20
- 4
src/pEpEngine.h View File

@ -510,6 +510,22 @@ DYNAMIC_API PEP_STATUS encrypt_and_sign(
size_t psize, char **ctext, size_t *csize
);
/**
* <!-- probe_encrypt() -->
*
* @brief Test if passphrase for a key is working in current session
*
* @param[in] session session handle
* @param[in] fpr fingerprint of key to test
*
* @retval PEP_STATUS_OK in case passphrase works
* @retval error if not
*
*
*/
DYNAMIC_API PEP_STATUS probe_encrypt(PEP_SESSION session, const char *fpr);
/**
* <!-- set_debug_color() -->
@ -741,10 +757,10 @@ typedef enum _identity_flags {
typedef unsigned int identity_flags_t;
// typedef enum _keypair_flags {
// } keypair_flags;
typedef unsigned int keypair_flags_t;
//typedef enum _keypair_flags {
//} keypair_flags;
//
//typedef unsigned int keypair_flags_t;
/**
* @struct pEp_identity


+ 4
- 0
src/pEp_internal.h View File

@ -208,6 +208,8 @@ struct _pEpSession {
// sqlite3_stmt *set_device_group;
// sqlite3_stmt *get_device_group;
sqlite3_stmt *set_pgp_keypair;
sqlite3_stmt *set_pgp_keypair_flags;
sqlite3_stmt *unset_pgp_keypair_flags;
sqlite3_stmt *set_identity_entry;
sqlite3_stmt *update_identity_entry;
sqlite3_stmt *exists_identity_entry;
@ -224,6 +226,8 @@ struct _pEpSession {
sqlite3_stmt *get_trust;
sqlite3_stmt *get_trust_by_userid;
sqlite3_stmt *least_trust;
sqlite3_stmt *update_key_sticky_bit_for_user;
sqlite3_stmt *is_key_sticky_for_user;
sqlite3_stmt *mark_compromised;
sqlite3_stmt *reset_trust;
sqlite3_stmt *crashdump;


+ 1
- 0
sync/cond_act_sync.yml2 View File

@ -603,6 +603,7 @@ action disable
action resetOwnGroupedKeys
||
// Will NOT reset keys with the sticky bit set
return key_reset_own_grouped_keys(session);
||


+ 234
- 8
test/src/KeyResetMessageTest.cc View File

@ -1208,19 +1208,142 @@ TEST_F(KeyResetMessageTest, check_reset_all_own_grouped) {
ofstream outfile;
int i = 0;
for (vector<message*>::iterator it = m_queue.begin(); it != m_queue.end(); it++, i++) {
message* curr_sent_msg = *it;
message* curr_sent_msg = *it;
string fname = string("test_mails/check_reset_all_own_grouped") + to_string(i) + ".eml";
outfile.open(fname);
char* msg_txt = NULL;
mime_encode_message(curr_sent_msg, false, &msg_txt, false);
outfile << msg_txt;
outfile.close();
outfile.close();
}
cout << " // For " << alex_id->address << endl;
cout << " const char* replkey1 = \"" << alex_id->fpr << "\";" << endl;
cout << " // For " << alex_id3->address << endl;
cout << " const char* replkey3 = \"" << alex_id3->fpr << "\";" << endl;
}
cout << " const char* replkey1 = \"" << alex_id->fpr << "\";" << endl;
cout << " // For " << alex_id3->address << endl;
cout << " const char* replkey3 = \"" << alex_id3->fpr << "\";" << endl;
}
free_identity(alex_id);
free_identity(alex_id2);
free_identity(alex_id3);
}
TEST_F(KeyResetMessageTest, check_reset_all_own_grouped_with_sticky) {
char* pubkey1 = strdup("74D79B4496E289BD8A71B70BA8E2C4530019697D");
char* pubkey2 = strdup("2E21325D202A44BFD9C607FCF095B202503B14D8");
char* pubkey3 = strdup("3C1E713D8519D7F907E3142D179EAA24A216E95A");
pEp_identity* alex_id = new_identity("pep.test.alexander@darthmama.org",
NULL,
"AlexID",
"Alexander Braithwaite");
pEp_identity* alex_id2 = new_identity("pep.test.alexander6@darthmama.org",
NULL,
"AlexID",
"Alexander Braithwaite");
pEp_identity* alex_id3 = new_identity("pep.test.alexander6a@darthmama.org",
NULL,
"AlexID",
"Alexander Braithwaite");
PEP_STATUS status = read_file_and_import_key(session, "test_keys/pub/pep.test.alexander6-0x0019697D_pub.asc");
status = read_file_and_import_key(session, "test_keys/pub/pep.test.alexander6-0x503B14D8_pub.asc");
status = read_file_and_import_key(session, "test_keys/pub/pep.test.alexander6-0xA216E95A_pub.asc");
status = read_file_and_import_key(session, "test_keys/priv/pep.test.alexander6-0x0019697D_priv.asc");
status = read_file_and_import_key(session, "test_keys/priv/pep.test.alexander6-0x503B14D8_priv.asc");
status = read_file_and_import_key(session, "test_keys/priv/pep.test.alexander6-0xA216E95A_priv.asc");
// sticky - false
alex_id->me = true;
status = set_own_key(session, alex_id, pubkey1);
ASSERT_EQ(status, PEP_STATUS_OK);
status = set_identity_flags(session, alex_id, alex_id->flags | PEP_idf_devicegroup);
ASSERT_EQ(status , PEP_STATUS_OK);
// sticky - true
alex_id2->me = true;
status = set_own_imported_key(session, alex_id2, pubkey2, true);
ASSERT_EQ(status, PEP_STATUS_OK);
status = set_identity_flags(session, alex_id2, alex_id2->flags | PEP_idf_not_for_sync);
ASSERT_EQ(status , PEP_STATUS_OK);
// sticky - true
alex_id3->me = true;
status = set_own_imported_key(session, alex_id3, pubkey3, true);
ASSERT_EQ(status, PEP_STATUS_OK);
status = set_identity_flags(session, alex_id3, alex_id3->flags | PEP_idf_devicegroup);
ASSERT_EQ(status , PEP_STATUS_OK);
status = myself(session, alex_id);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_STREQ(pubkey1, alex_id->fpr);
status = myself(session, alex_id2);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_STREQ(pubkey2, alex_id2->fpr);
status = myself(session, alex_id3);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_STREQ(pubkey3, alex_id3->fpr);
status = key_reset_own_grouped_keys(session);
free(alex_id->fpr);
alex_id->fpr = strdup(pubkey1);
status = get_trust(session, alex_id);
ASSERT_EQ(alex_id->comm_type , PEP_ct_mistrusted);
free(alex_id2->fpr);
alex_id2->fpr = strdup(pubkey2);
status = get_trust(session, alex_id2);
ASSERT_EQ(alex_id2->comm_type , PEP_ct_pEp);
free(alex_id3->fpr);
alex_id3->fpr = strdup(pubkey3);
status = get_trust(session, alex_id3);
ASSERT_EQ(alex_id3->comm_type , PEP_ct_pEp);
bool revoked = false;
status = key_revoked(session, pubkey1, &revoked);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_TRUE(revoked);
revoked = false;
status = key_revoked(session, pubkey2, &revoked);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_FALSE(revoked);
revoked = false;
status = key_revoked(session, pubkey3, &revoked);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_FALSE(revoked);
status = myself(session, alex_id);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_STRNE(pubkey1, alex_id->fpr);
status = myself(session, alex_id2);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_STREQ(pubkey2, alex_id2->fpr);
status = myself(session, alex_id3);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_STREQ(pubkey3, alex_id3->fpr);
ASSERT_EQ(m_queue.size(),1);
if (false) {
ofstream outfile;
string fname = "test_mails/check_reset_all_own_grouped_sticky.eml";
outfile.open(fname);
char* msg_txt = NULL;
mime_encode_message(m_queue[0], false, &msg_txt, false);
outfile << msg_txt;
outfile.close();
cout << " // For " << alex_id->address << endl;
cout << " const char* replkey1 = \"" << alex_id->fpr << "\";" << endl;
}
free_identity(alex_id);
free_identity(alex_id2);
@ -1237,7 +1360,7 @@ TEST_F(KeyResetMessageTest, check_reset_all_own_grouped_recv) {
const char* replkey1 = "0F9C2FBFB898AD3A1242257F300EFFDE4CE2C33F";
// For pep.test.alexander6a@darthmama.org
const char* replkey3 = "3671C09D3C79260C65045AE9A62A64E4CBEDAFDA";
pEp_identity* alex_id = new_identity("pep.test.alexander@darthmama.org",
NULL,
"AlexID",
@ -1312,7 +1435,7 @@ TEST_F(KeyResetMessageTest, check_reset_all_own_grouped_recv) {
ASSERT_EQ(status, PEP_STATUS_OK);
status = decrypt_message(session, new_msg, &dec_msg, &keylist, &rating, &flags);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_EQ(status, PEP_STATUS_OK);
}
char* new_main_key = NULL;
@ -1332,6 +1455,109 @@ TEST_F(KeyResetMessageTest, check_reset_all_own_grouped_recv) {
ASSERT_STREQ(replkey3, alex_id3->fpr);
}
TEST_F(KeyResetMessageTest, check_reset_all_own_grouped_recv_with_sticky) {
PEP_STATUS status = PEP_STATUS_OK;
char* pubkey1 = strdup("74D79B4496E289BD8A71B70BA8E2C4530019697D");
char* pubkey2 = strdup("2E21325D202A44BFD9C607FCF095B202503B14D8");
char* pubkey3 = strdup("3C1E713D8519D7F907E3142D179EAA24A216E95A");
// For pep.test.alexander@darthmama.org
const char* replkey1 = "8FDB872F88BD76F2C6C2DE0E8453FAEA21DD0DCF";
pEp_identity* alex_id = new_identity("pep.test.alexander@darthmama.org",
NULL,
"AlexID",
"Alexander Braithwaite");
pEp_identity* alex_id2 = new_identity("pep.test.alexander6@darthmama.org",
NULL,
"AlexID",
"Alexander Braithwaite");
pEp_identity* alex_id3 = new_identity("pep.test.alexander6a@darthmama.org",
NULL,
"AlexID",
"Alexander Braithwaite");
status = read_file_and_import_key(session, "test_keys/pub/pep.test.alexander6-0x0019697D_pub.asc");
status = read_file_and_import_key(session, "test_keys/pub/pep.test.alexander6-0x503B14D8_pub.asc");
status = read_file_and_import_key(session, "test_keys/pub/pep.test.alexander6-0xA216E95A_pub.asc");
status = read_file_and_import_key(session, "test_keys/priv/pep.test.alexander6-0x0019697D_priv.asc");
status = read_file_and_import_key(session, "test_keys/priv/pep.test.alexander6-0x503B14D8_priv.asc");
status = read_file_and_import_key(session, "test_keys/priv/pep.test.alexander6-0xA216E95A_priv.asc");
alex_id->me = true;
status = set_own_key(session, alex_id, pubkey1);
ASSERT_EQ(status, PEP_STATUS_OK);
status = set_identity_flags(session, alex_id, alex_id->flags | PEP_idf_devicegroup);
ASSERT_EQ(status , PEP_STATUS_OK);
alex_id2->me = true;
status = set_own_key(session, alex_id2, pubkey2);
ASSERT_EQ(status, PEP_STATUS_OK);
status = set_identity_flags(session, alex_id2, alex_id2->flags | PEP_idf_devicegroup);
ASSERT_EQ(status , PEP_STATUS_OK);
alex_id3->me = true;
status = set_own_key(session, alex_id3, pubkey3);
ASSERT_EQ(status, PEP_STATUS_OK);
status = set_identity_flags(session, alex_id3, alex_id3->flags | PEP_idf_devicegroup);
ASSERT_EQ(status , PEP_STATUS_OK);
status = myself(session, alex_id);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_STREQ(pubkey1, alex_id->fpr);
status = myself(session, alex_id2);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_STREQ(pubkey2, alex_id2->fpr);
status = myself(session, alex_id3);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_STREQ(pubkey3, alex_id3->fpr);
char* old_main_key = NULL;
status = get_main_user_fpr(session, "AlexID", &old_main_key);
ASSERT_NE(old_main_key, nullptr);
const int num_msgs = 1;
// receive reset messages
message* dec_msg = NULL;
stringlist_t* keylist = NULL;
PEP_rating rating;
PEP_decrypt_flags_t flags = 0;
string fname = "test_mails/check_reset_all_own_grouped_sticky.eml";
string mailstr = slurp(fname.c_str());
message* new_msg = NULL;
status = mime_decode_message(mailstr.c_str(), mailstr.size(), &new_msg, NULL);
ASSERT_NE(new_msg, nullptr);
ASSERT_EQ(status, PEP_STATUS_OK);
status = decrypt_message(session, new_msg, &dec_msg, &keylist, &rating, &flags);
ASSERT_EQ(status, PEP_STATUS_OK);
char* new_main_key = NULL;
status = get_main_user_fpr(session, "AlexID", &new_main_key);
ASSERT_STRNE(old_main_key, new_main_key);
status = myself(session, alex_id);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_STREQ(replkey1, alex_id->fpr);
status = myself(session, alex_id2);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_STREQ(pubkey2, alex_id2->fpr);
status = myself(session, alex_id3);
ASSERT_EQ(status, PEP_STATUS_OK);
ASSERT_STREQ(pubkey3, alex_id3->fpr);
}
TEST_F(KeyResetMessageTest, check_reset_grouped_own_multiple_keys_multiple_idents_reset_all_recv) {
PEP_STATUS status = PEP_STATUS_OK;
char* pubkey1 = strdup("74D79B4496E289BD8A71B70BA8E2C4530019697D");


+ 98
- 0
test/src/StickyBitTest.cc View File

@ -0,0 +1,98 @@
#include <stdlib.h>
#include <string>
#include <cstring>
#include "pEpEngine.h"
#include "test_util.h"
#include "TestConstants.h"
#include "Engine.h"
#include <gtest/gtest.h>
namespace {
//The fixture for StickyBitTest
class StickyBitTest : public ::testing::Test {
public:
Engine* engine;
PEP_SESSION session;
protected:
// You can remove any or all of the following functions if its body
// is empty.
StickyBitTest() {
// You can do set-up work for each test here.
test_suite_name = ::testing::UnitTest::GetInstance()->current_test_info()->GTEST_SUITE_SYM();
test_name = ::testing::UnitTest::GetInstance()->current_test_info()->name();
test_path = get_main_test_home_dir() + "/" + test_suite_name + "/" + test_name;
}
~StickyBitTest() override {
// You can do clean-up work that doesn't throw exceptions here.
}
// If the constructor and destructor are not enough for setting up
// and cleaning up each test, you can define the following methods:
void SetUp() override {
// Code here will be called immediately after the constructor (right
// before each test).
// Leave this empty if there are no files to copy to the home directory path
std::vector<std::pair<std::string, std::string>> init_files = std::vector<std::pair<std::string, std::string>>();
// Get a new test Engine.
engine = new Engine(test_path);
ASSERT_NE(engine, nullptr);
// Ok, let's initialize test directories etc.
engine->prep(NULL, NULL, NULL, init_files);
// Ok, try to start this bugger.
engine->start();
ASSERT_NE(engine->session, nullptr);
session = engine->session;
// Engine is up. Keep on truckin'
}
void TearDown() override {
// Code here will be called immediately after each test (right
// before the destructor).
engine->shut_down();
delete engine;
engine = NULL;
session = NULL;
}
private:
const char* test_suite_name;
const char* test_name;
string test_path;
// Objects declared here can be used by all tests in the StickyBitTest suite.
};
} // namespace
TEST_F(StickyBitTest, check_set_sticky_bit_normal) {
PEP_STATUS status = PEP_STATUS_OK;
ASSERT_TRUE(slurp_and_import_key(session, "test_keys/pub/pep-test-bob-0xC9C2EE39_pub.asc"));
ASSERT_TRUE(slurp_and_import_key(session, "test_keys/priv/pep-test-bob-0xC9C2EE39_priv.asc"));
const char* bob_name = "STOP MESSING WITH ME ALICE";
const char* bob_fpr = "BFCDB7F301DEEEBBF947F29659BFF488C9C2EE39";
pEp_identity* me = new_identity("pep.test.bob@pep-project.org", NULL, PEP_OWN_USERID, bob_name);
status = set_own_imported_key(session, me, bob_fpr, true);
ASSERT_EQ(status , PEP_STATUS_OK);
status = myself(session, me);
ASSERT_EQ(status , PEP_STATUS_OK);
bool sticky = false;
status = get_key_sticky_bit_for_user(session, me->user_id, bob_fpr, &sticky);
ASSERT_TRUE(sticky);
free_identity(me);
}

+ 284
- 0
test/test_mails/check_reset_all_own_grouped_sticky.eml View File

@ -0,0 +1,284 @@
Message-ID: <pEp.QP53VJ.1X67W5OUABHPO.D85FC9E0-8D58-4504-AF84-A4476D37C8ED@darthmama.org>
From: Alexander Braithwaite <pep.test.alexander@darthmama.org>
To: Alexander Braithwaite <pep.test.alexander@darthmama.org>
Subject: =?utf-8?Q?p=E2=89=A1p?=
In-Reply-To: <pEp-auto-consume@pEp.foundation>
X-pEp-Version: 2.1
pEp-auto-consume: yes
MIME-Version: 1.0
Content-Type: multipart/encrypted; boundary="238e1f2946e87ccd3d1b58ba507ed7ab";
protocol="application/pgp-encrypted"
--238e1f2946e87ccd3d1b58ba507ed7ab
Content-Type: application/pgp-encrypted
Version: 1
--238e1f2946e87ccd3d1b58ba507ed7ab
Content-Type: application/octet-stream
Content-Transfer-Encoding: 7bit
Content-Disposition: inline; filename="msg.asc"
-----BEGIN PGP MESSAGE-----
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
ewwiJsHeRRXxg91Pv+mHYlInURF2M0vTFcUaP9k=
=eNGJ
-----END PGP MESSAGE-----
--238e1f2946e87ccd3d1b58ba507ed7ab--

Loading…
Cancel
Save