p≡p engine
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

672 lines
22 KiB

#include "platform.h"
// it seems pEp_internal.h needs to be the first pEp include due to the
// #define for the dllimport / dllexport DYNAMIC_API stuff.
#include "pEp_internal.h"
#include "../asn.1/DeviceGroup-Protocol.h"
#include "sync_impl.h"
#include "keymanagement.h"
#include "message_api.h"
#include "map_asn1.h"
#include "baseprotocol.h"
#define SYNC_VERSION_MAJOR 1
#define SYNC_VERSION_MINOR 0
#define SYNC_INHIBIT_TIME (60*10)
#define SYNC_MSG_EXPIRE_TIME (60 * 10)
struct _sync_msg_t {
bool is_a_message;
union {
DeviceGroup_Protocol_t *message;
struct {
DeviceState_event event;
Identity partner;
void *extra;
} event;
} u;
};
PEP_STATUS receive_sync_msg(
PEP_SESSION session,
sync_msg_t *sync_msg
)
{
PEP_STATUS status;
void *extra = NULL;
Identity partner = NULL;
DeviceState_event event = DeviceState_event_NONE;
assert(session && sync_msg);
if (!(session && sync_msg))
return PEP_ILLEGAL_VALUE;
bool msgIsFromGroup = false;
if(sync_msg->is_a_message){
DeviceGroup_Protocol_t *msg = sync_msg->u.message;
assert(msg && msg->payload.present != DeviceGroup_Protocol__payload_PR_NOTHING);
if (!(msg && msg->payload.present != DeviceGroup_Protocol__payload_PR_NOTHING)){
status = PEP_OUT_OF_MEMORY;
goto error;
}
msgIsFromGroup = msg->header.devicegroup;
switch (msg->payload.present) {
case DeviceGroup_Protocol__payload_PR_beacon:
event = Beacon;
break;
case DeviceGroup_Protocol__payload_PR_handshakeRequest:
event = HandshakeRequest;
break;
case DeviceGroup_Protocol__payload_PR_updateRequest:
event = UpdateRequest;
break;
case DeviceGroup_Protocol__payload_PR_groupKeys:
case DeviceGroup_Protocol__payload_PR_groupUpdate:
{
identity_list *group_keys = IdentityList_to_identity_list(
msg->payload.present ==
DeviceGroup_Protocol__payload_PR_groupKeys ?
&msg->payload.choice.groupKeys.ownIdentities :
&msg->payload.choice.groupUpdate.ownIdentities,
NULL);
if (!group_keys) {
status = PEP_OUT_OF_MEMORY;
ASN_STRUCT_FREE(asn_DEF_DeviceGroup_Protocol, msg);
goto error;
}
extra = (void *) group_keys;
event = msg->payload.present ==
DeviceGroup_Protocol__payload_PR_groupKeys ?
GroupKeys : GroupUpdate;
break;
}
default:
status = PEP_SYNC_ILLEGAL_MESSAGE;
ASN_STRUCT_FREE(asn_DEF_DeviceGroup_Protocol, msg);
goto error;
}
partner = Identity_to_Struct(&msg->header.me, NULL);
if (!partner){
status = PEP_OUT_OF_MEMORY;
ASN_STRUCT_FREE(asn_DEF_DeviceGroup_Protocol, msg);
goto error;
}
}
else{
partner = sync_msg->u.event.partner;
extra = sync_msg->u.event.extra;
event = sync_msg->u.event.event;
}
// Event inhibition, to limit mailbox and prevent cycles
time_t *last = NULL;
switch(event){
case CannotDecrypt:
last = &session->LastCannotDecrypt;
break;
case UpdateRequest:
last = &session->LastUpdateRequest;
break;
default:
break;
}
time_t now = time(NULL);
if(last != NULL){
if(*last != 0 && (*last + SYNC_INHIBIT_TIME) > now ){
free_identity(partner);
status = PEP_STATEMACHINE_INHIBITED_EVENT;
goto error;
}
*last = now;
}
// partner identity must be explicitely added DB to later
// be able to communicate securely with it.
if(partner){
// protect virtual user IDs
if((strncmp("TOFU_", partner->user_id, 6) == 0 &&
strlen(partner->user_id) == strlen(partner->address) + 6 &&
strcmp(partner->user_id + 6, partner->address)) ||
// protect own ID
(strcmp(PEP_OWN_USERID, partner->user_id) == 0)){
status = PEP_SYNC_ILLEGAL_MESSAGE;
goto error;
}
// partner IDs are UUIDs bound to session lifespan
// and therefore partner identities are not supposed
// to mutate over time, but just not be used anymore.
// It should then be safe to accept given identity if not
// already pre-existing
pEp_identity *stored_identity = NULL;
status = get_identity(session,
partner->address,
partner->user_id,
&stored_identity);
if (!stored_identity) {
// make a safe copy of partner, with no flags or comm_type
pEp_identity *tmpident = new_identity(partner->address,
partner->fpr,
partner->user_id,
partner->username);
if (tmpident == NULL){
status = PEP_OUT_OF_MEMORY;
goto error;
}
// finaly add partner to DB
status = set_identity(session, tmpident);
assert(status == PEP_STATUS_OK);
if(status == PEP_STATUS_OK && msgIsFromGroup)
status = set_identity_flags(session, tmpident, PEP_idf_devicegroup);
free_identity(tmpident);
assert(status == PEP_STATUS_OK);
if (status != PEP_STATUS_OK) {
goto error;
}
}
else if (status == PEP_STATUS_OK) {
free_identity(stored_identity);
}
else
goto error;
}
status = fsm_DeviceState_inject(session, event, partner, extra);
free_identity(partner);
error:
free(sync_msg);
return status;
}
// TODO: DYNAMIC_API was here, but broke the windows build.
// We need to check whether it belongs here or it's a bug.
/* DYNAMIC_API */ void free_sync_msg(sync_msg_t *sync_msg)
{
assert(sync_msg);
if (!(sync_msg))
return;
if(sync_msg->is_a_message){
DeviceGroup_Protocol_t *msg = sync_msg->u.message;
assert(msg);
if (!(msg))
return;
ASN_STRUCT_FREE(asn_DEF_DeviceGroup_Protocol, msg);
}
else{
Identity partner = NULL;
partner = sync_msg->u.event.partner;
if(partner != NULL)
free_identity(partner);
}
free(sync_msg);
return;
}
// from sync.c
int call_inject_sync_msg(PEP_SESSION session, void *msg);
PEP_STATUS inject_DeviceState_event(
PEP_SESSION session,
DeviceState_event event,
Identity partner,
void *extra)
{
PEP_STATUS status;
assert(session);
if (!(session))
return PEP_ILLEGAL_VALUE;
sync_msg_t *sync_msg = malloc(sizeof(sync_msg_t));
if(sync_msg == NULL)
return PEP_OUT_OF_MEMORY;
sync_msg->is_a_message = false;
sync_msg->u.event.partner = partner;
sync_msg->u.event.extra = extra;
sync_msg->u.event.event = event;
status = call_inject_sync_msg(session, sync_msg);
if (status == PEP_SYNC_NO_INJECT_CALLBACK){
free(sync_msg);
}
return status;
}
PEP_STATUS receive_DeviceState_msg(
PEP_SESSION session,
message *src,
PEP_rating rating,
stringlist_t *keylist)
{
assert(session && src);
if (!(session && src))
return PEP_ILLEGAL_VALUE;
bool consume = false;
bool discard = false;
bool force_keep_msg = false;
for (bloblist_t *bl = src->attachments; bl && bl->value; bl = bl->next) {
if (bl->mime_type && strcasecmp(bl->mime_type, "application/pEp.sync") == 0
&& bl->size) {
DeviceGroup_Protocol_t *msg = NULL;
uper_decode_complete(NULL, &asn_DEF_DeviceGroup_Protocol, (void **)
&msg, bl->value, bl->size);
if (msg) {
PEP_STATUS status = PEP_STATUS_OK;
char *user_id = strndup((char *) msg->header.me.user_id->buf,
msg->header.me.user_id->size);
assert(user_id);
if (!user_id) {
ASN_STRUCT_FREE(asn_DEF_DeviceGroup_Protocol, msg);
return PEP_OUT_OF_MEMORY;
}
// check message expiry
if(src->recv) {
time_t expiry = timegm(src->recv) + SYNC_MSG_EXPIRE_TIME;
time_t now = time(NULL);
if(expiry != 0 && now != 0 && expiry < now){
consume = true;
goto free_all;
}
}
int32_t value = (int32_t) msg->header.sequence;
status = sequence_value(session, (char *) user_id,
&value);
if (status == PEP_STATUS_OK) {
switch (msg->payload.present) {
// HandshakeRequest needs encryption
case DeviceGroup_Protocol__payload_PR_handshakeRequest:
if (rating < PEP_rating_reliable ||
strncmp(session->sync_uuid,
(const char *)msg->payload.choice.handshakeRequest.partner.user_id->buf,
msg->payload.choice.handshakeRequest.partner.user_id->size) != 0){
discard = true;
goto free_all;
}
// Ignore and consume handshakes with devices
// already using trusted own key to encrypt
if (rating >= PEP_rating_trusted){
consume = true;
goto free_all;
}
break;
// accepting GroupKeys needs encryption and trust of peer device
case DeviceGroup_Protocol__payload_PR_groupKeys:
{
if (!keylist || rating < PEP_rating_reliable ||
// message is only consumed by instance it is addressed to
(strncmp(session->sync_uuid,
(const char *)msg->payload.choice.groupKeys.partner.user_id->buf,
msg->payload.choice.groupKeys.partner.user_id->size) != 0)){
discard = true;
goto free_all;
}
// check trust of identity using user_id given in payload
// to exacly match identity of device, the one trusted in
// case of accepted handshake
pEp_identity *_from = new_identity(NULL,
keylist->value,
user_id,
NULL);
if (_from == NULL){
status = PEP_OUT_OF_MEMORY;
goto free_all;
}
status = get_trust(session, _from);
if (status != PEP_STATUS_OK || _from->comm_type < PEP_ct_strong_encryption) {
status = PEP_STATUS_OK;
free_identity(_from);
discard = true;
goto free_all;
}
free_identity(_from);
break;
}
case DeviceGroup_Protocol__payload_PR_groupUpdate:
case DeviceGroup_Protocol__payload_PR_updateRequest:
{
// inject message but don't consume it, so
// that other group members can also be updated
force_keep_msg = true;
if (!keylist || rating < PEP_rating_reliable){
discard = true;
goto free_all;
}
// GroupUpdate and UpdateRequests come from group.
// check trust relation in between signer key and
// own id to be sure.
pEp_identity *_from = new_identity(NULL,
keylist->value,
PEP_OWN_USERID,
NULL);
if (_from == NULL){
status = PEP_OUT_OF_MEMORY;
goto free_all;
}
status = get_trust(session, _from);
if (status != PEP_STATUS_OK || _from->comm_type < PEP_ct_pEp) {
status = PEP_STATUS_OK;
free_identity(_from);
discard = true;
goto free_all;
}
free_identity(_from);
}
default:
break;
}
consume = true;
sync_msg_t *sync_msg = malloc(sizeof(sync_msg_t));
if(sync_msg == NULL){
status = PEP_OUT_OF_MEMORY;
goto free_all;
}
sync_msg->is_a_message = true;
sync_msg->u.message = msg;
status = call_inject_sync_msg(session, sync_msg);
if (status != PEP_STATUS_OK){
if (status == PEP_SYNC_NO_INJECT_CALLBACK){
free(sync_msg);
}
goto free_all;
}
// don't free message now that it is in the queue
goto free_userid;
}
else if (status == PEP_OWN_SEQUENCE) {
status = PEP_STATUS_OK;
discard = true;
goto free_all;
}
free_all:
ASN_STRUCT_FREE(asn_DEF_DeviceGroup_Protocol, msg);
free_userid:
free(user_id);
if (status != PEP_STATUS_OK)
return status;
}
}
}
if (force_keep_msg) {
return PEP_MESSAGE_IGNORE;
}
if (consume && !session->keep_sync_msg) {
for (stringpair_list_t *spl = src->opt_fields ; spl && spl->value ;
spl = spl->next) {
if (spl->value->key &&
strcasecmp(spl->value->key, "pEp-auto-consume") == 0) {
if (spl->value->value &&
strcasecmp(spl->value->value, "yes") == 0)
return PEP_MESSAGE_CONSUME;
}
}
return PEP_MESSAGE_IGNORE;
}
if(discard)
return PEP_MESSAGE_IGNORE;
if (!session->keep_sync_msg) {
bloblist_t *last = NULL;
for (bloblist_t *bl = src->attachments; bl && bl->value; ) {
if (bl->mime_type && strcasecmp(bl->mime_type, "application/pEp.sync") == 0) {
bloblist_t *b = bl;
bl = bl->next;
if (!last)
src->attachments = bl;
else
last->next = bl;
free(b->mime_type);
free(b->filename);
free(b->value);
free(b);
}
else {
last = bl;
bl = bl->next;
}
}
}
return PEP_STATUS_OK;
}
DeviceGroup_Protocol_t *new_DeviceGroup_Protocol_msg(DeviceGroup_Protocol__payload_PR type)
{
DeviceGroup_Protocol_t *msg = (DeviceGroup_Protocol_t *)
calloc(1, sizeof(DeviceGroup_Protocol_t));
assert(msg);
if (!msg)
return NULL;
msg->payload.present = type;
return msg;
}
void free_DeviceGroup_Protocol_msg(DeviceGroup_Protocol_t *msg)
{
ASN_STRUCT_FREE(asn_DEF_DeviceGroup_Protocol, msg);
}
PEP_STATUS unicast_msg(
PEP_SESSION session,
const Identity partner,
DeviceState_state state,
DeviceGroup_Protocol_t *msg,
bool encrypted
)
{
PEP_STATUS status = PEP_STATUS_OK;
char *payload = NULL;
message *_message = NULL;
pEp_identity *me = NULL;
pEp_identity *_me = NULL;
assert(session && partner && state && msg);
if (!(session && partner && state && msg))
return PEP_ILLEGAL_VALUE;
assert(session->messageToSend);
if (!session->messageToSend) {
status = PEP_SEND_FUNCTION_NOT_REGISTERED;
goto error;
}
msg->header.version.major = SYNC_VERSION_MAJOR;
msg->header.version.minor = SYNC_VERSION_MINOR;
status = get_identity(session, partner->address, PEP_OWN_USERID, &me);
if (status != PEP_STATUS_OK)
goto error;
int32_t seq = 0;
status = sequence_value(session, session->sync_uuid, &seq);
if (status != PEP_OWN_SEQUENCE && status != PEP_STATUS_OK)
goto error;
msg->header.sequence = (long) seq;
_me = identity_dup(me);
if (!_me)
goto enomem;
free(_me->user_id);
_me->user_id = strndup(session->sync_uuid, 36);
assert(_me->user_id);
if (!_me->user_id)
goto enomem;
if (Identity_from_Struct(_me, &msg->header.me) == NULL)
goto enomem;
free_identity(_me);
_me = NULL;
msg->header.state = (long) state;
bool devicegroup = storedGroupKeys(session);
if (devicegroup)
msg->header.devicegroup = 1;
else
msg->header.devicegroup = 0;
if (asn_check_constraints(&asn_DEF_DeviceGroup_Protocol, msg, NULL, NULL)) {
status = PEP_CONTRAINTS_VIOLATED;
goto error;
}
ssize_t size = uper_encode_to_new_buffer(&asn_DEF_DeviceGroup_Protocol,
NULL, msg, (void **) &payload);
if (size == -1) {
status = PEP_CANNOT_ENCODE;
goto error;
}
status = prepare_message(me, partner, payload, size, &_message);
if (status != PEP_STATUS_OK)
goto error;
payload = NULL;
free_identity(me);
me = NULL;
if (encrypted) {
if (msg->payload.present == DeviceGroup_Protocol__payload_PR_groupKeys ||
msg->payload.present == DeviceGroup_Protocol__payload_PR_groupUpdate) {
PEP_rating rating = PEP_rating_undefined;
status = outgoing_message_rating(session, _message, &rating);
if (status != PEP_STATUS_OK)
goto error;
if (rating < PEP_rating_trusted) {
status = PEP_SYNC_NO_TRUST;
goto error;
}
stringlist_t *keylist = NULL;
status = _own_keys_retrieve(session, &keylist, PEP_idf_not_for_sync);
if (status != PEP_STATUS_OK)
goto error;
for (stringlist_t *_keylist=keylist; _keylist!=NULL; _keylist=_keylist->next) {
char *fpr = _keylist->value;
static char filename[MAX_LINELENGTH];
int result = snprintf(filename, MAX_LINELENGTH, "%s-sec.asc", fpr);
if (result < 0)
goto enomem;
char *key = NULL;
size_t size = 0;
status = export_secrect_key(session, fpr, &key, &size);
if (status != PEP_STATUS_OK)
goto error;
bloblist_t *bl = bloblist_add(_message->attachments,
(char *) key, size, "application/pgp-keys", filename);
if (!bl)
goto enomem;
if (!_message->attachments)
_message->attachments = bl;
}
}
message *_encrypted = NULL;
status = encrypt_message(session, _message, NULL, &_encrypted, PEP_enc_PEP, 0);
if (status != PEP_STATUS_OK)
goto error;
free_message(_message);
_message = _encrypted;
}
else {
attach_own_key(session, _message);
}
status = session->messageToSend(session->sync_obj, _message);
return status;
enomem:
status = PEP_OUT_OF_MEMORY;
error:
free_identity(_me);
free(payload);
free_message(_message);
free_identity(me);
return status;
}
PEP_STATUS multicast_self_msg(
PEP_SESSION session,
DeviceState_state state,
DeviceGroup_Protocol_t *msg,
bool encrypted
)
{
PEP_STATUS status = PEP_STATUS_OK;
assert(session && state && msg);
if (!(session && state && msg))
return PEP_ILLEGAL_VALUE;
identity_list *own_identities = NULL;
status = _own_identities_retrieve(session, &own_identities, PEP_idf_not_for_sync);
if (status != PEP_STATUS_OK)
return status;
// FIXME: exclude previously rejected identities
for (identity_list *_i = own_identities; _i && _i->ident; _i = _i->next) {
pEp_identity *me = _i->ident;
// FIXME: no deep copy for multicast supported yet
// DeviceGroup_Protocol_t *_msg = malloc(sizeof(DeviceGroup_Protocol_t));
// assert(_msg);
// if (_msg == NULL){
// status = PEP_OUT_OF_MEMORY;
// goto error;
// }
// memcpy(_msg, msg, sizeof(DeviceGroup_Protocol_t));
status = unicast_msg(session, me, state, msg, encrypted);
//status = unicast_msg(session, me, state, _msg, encrypted);
//free_DeviceGroup_Protocol_msg(_msg);
}
free_identity_list(own_identities);
return PEP_STATUS_OK;
// error:
// free_identity_list(own_identities);
// return status;
}