// This file is under BSD License 2.0 // Sync protocol for p≡p // Copyright (c) 2016-2020, p≡p foundation // Written by Volker Birk include ./fsm.yml2 protocol Sync 1 { // all messages have a timestamp, time out and are removed after timeout fsm KeySync 1, threshold=300 { version 1, 2; state InitState { on Init { if deviceGrouped { send SynchronizeGroupKeys; go Grouped; } do newChallengeAndNegotiationBase; debug > initial Beacon send Beacon; go Sole; } } state Sole timeout=off { on Init { do showBeingSole; } on KeyGen { debug > key generated send Beacon; } on CannotDecrypt { debug > cry, baby send Beacon; } on Beacon { if sameChallenge { debug > this is our own Beacon; ignore } else { if weAreOfferer { do useOwnChallenge; debug > we are Offerer send Beacon; } else /* we are requester */ { do openNegotiation; do tellWeAreNotGrouped; // requester is sending NegotiationRequest do useOwnResponse; send NegotiationRequest; do useOwnChallenge; } } } // we get this from another sole device on NegotiationRequest { if sameChallenge { // challenge accepted do storeNegotiation; // offerer is accepting by confirming NegotiationOpen // repeating response is implicit send NegotiationOpen; go HandshakingOfferer; } } // we get this from an existing device group on NegotiationRequestGrouped { if sameChallenge { // challenge accepted do storeNegotiation; // offerer is accepting by confirming NegotiationOpen // repeating response is implicit send NegotiationOpen; go HandshakingToJoin; } } on NegotiationOpen { if sameResponse { debug > Requester is receiving NegotiationOpen do storeNegotiation; go HandshakingRequester; } else { debug > cannot approve NegotiationOpen } } } // handshaking without existing Device group state HandshakingOfferer timeout=600 { on Init do showSoleHandshake; // Cancel is Rollback on Cancel { send Rollback; go Sole; } on Rollback { if sameNegotiation go Sole; } // Reject is CommitReject on Reject { send CommitReject; do disable; go End; } on CommitReject { if sameNegotiation { do disable; go End; } } // Accept means init Phase1Commit on Accept { do trustThisKey; go HandshakingPhase1Offerer; } // got a CommitAccept from requester on CommitAcceptRequester { if sameNegotiation go HandshakingPhase2Offerer; } } // handshaking without existing Device group state HandshakingRequester timeout=600 { on Init do showSoleHandshake; // Cancel is Rollback on Cancel { send Rollback; go Sole; } on Rollback { if sameNegotiation go Sole; } // Reject is CommitReject on Reject { send CommitReject; do disable; go End; } on CommitReject { if sameNegotiation { do disable; go End; } } // Accept means init Phase1Commit on Accept { do trustThisKey; send CommitAcceptRequester; go HandshakingPhase1Requester; } } state HandshakingPhase1Offerer { on Rollback { if sameNegotiation { do untrustThisKey; go Sole; } } on CommitReject { if sameNegotiation { do untrustThisKey; do disable; go End; } } on CommitAcceptRequester { if sameNegotiation { send CommitAcceptOfferer; go FormingGroupOfferer; } } } state HandshakingPhase1Requester { on Rollback { if sameNegotiation { do untrustThisKey; go Sole; } } on CommitReject { if sameNegotiation { do untrustThisKey; do disable; go End; } } on CommitAcceptOfferer { if sameNegotiation { do prepareOwnKeys; send OwnKeysRequester; go FormingGroupRequester; } } } state HandshakingPhase2Offerer { on Cancel { send Rollback; go Sole; } on Reject { send CommitReject; do disable; go End; } on Accept { do trustThisKey; send CommitAcceptOfferer; go FormingGroupOfferer; } } state FormingGroupOfferer { on Init { // we need to keep in memory which keys we have before forming // a new group do prepareOwnKeys; do backupOwnKeys; } on Cancel { send Rollback; go Sole; } on Rollback go Sole; on OwnKeysRequester { if sameNegotiationAndPartner { do saveGroupKeys; do receivedKeysAreDefaultKeys; // send the keys we had before forming a new group do prepareOwnKeysFromBackup; send OwnKeysOfferer; do showGroupCreated; go Grouped; } } } state FormingGroupRequester { on Cancel { send Rollback; go Sole; } on Rollback go Sole; on OwnKeysOfferer { if sameNegotiation { do saveGroupKeys; do prepareOwnKeys; do ownKeysAreDefaultKeys; do showGroupCreated; go Grouped; } } } state Grouped timeout=off { on Init { do newChallengeAndNegotiationBase; do showBeingInGroup; } on CannotDecrypt { debug > cry, baby send SynchronizeGroupKeys; } on SynchronizeGroupKeys { do prepareOwnKeys; send GroupKeysUpdate; } on GroupKeysUpdate { if fromGroupMember // double check do saveGroupKeys; } on KeyGen { do prepareOwnKeys; send GroupKeysUpdate; } on Beacon { do openNegotiation; do tellWeAreGrouped; do useOwnResponse; send NegotiationRequestGrouped; do useOwnChallenge; } on NegotiationOpen { if sameResponse { do storeNegotiation; do useThisKey; send GroupHandshake; go HandshakingGrouped; } else { debug > cannot approve NegotiationOpen } } on GroupHandshake { do storeNegotiation; do storeThisKey; go HandshakingGrouped; } on GroupTrustThisKey { if fromGroupMember // double check do trustThisKey; } on LeaveDeviceGroup { send InitUnledGroupKeyReset; do disable; do resetOwnKeysUngrouped; } on InitUnledGroupKeyReset { debug > unled group key reset; new group keys will be elected do useOwnResponse; send ElectGroupKeyResetLeader; go GroupKeyResetElection; } } state GroupKeyResetElection { on ElectGroupKeyResetLeader { if sameResponse { // the first one is from us, we're leading this do resetOwnGroupedKeys; go Grouped; } else { // the first one is not from us go Grouped; } } } // sole device handshaking with group state HandshakingToJoin { on Init do showJoinGroupHandshake; // Cancel is Rollback on Cancel { send Rollback; go Sole; } on Rollback { if sameNegotiation go Sole; } // Reject is CommitReject on Reject { send CommitReject; do disable; go End; } on CommitAcceptForGroup { if sameNegotiation go HandshakingToJoinPhase2; } on CommitReject { if sameNegotiation { do disable; go End; } } // Accept is Phase1Commit on Accept { do trustThisKey; go HandshakingToJoinPhase1; } } state HandshakingToJoinPhase1 { on Rollback { if sameNegotiation { do untrustThisKey; go Sole; } } on CommitReject { if sameNegotiation { do untrustThisKey; do disable; go End; } } on CommitAcceptForGroup { if sameNegotiation { send CommitAccept; go JoiningGroup; } } } state HandshakingToJoinPhase2 { on Cancel { send Rollback; go Sole; } on Reject { send CommitReject; do disable; go End; } on Accept { do trustThisKey; send CommitAccept; go JoiningGroup; } } state JoiningGroup { on Init { // we need to keep in memory which keys we have before joining do prepareOwnKeys; do backupOwnKeys; } on GroupKeysForNewMember { if sameNegotiationAndPartner { do saveGroupKeys; do receivedKeysAreDefaultKeys; // send the keys we had before joining do prepareOwnKeysFromBackup; send GroupKeysAndClose; do showDeviceAdded; go Grouped; } } } state HandshakingGrouped { on Init do showGroupedHandshake; // Cancel is Rollback on Cancel { send Rollback; go Grouped; } on Rollback { if sameNegotiation go Grouped; } // Reject is CommitReject on Reject { send CommitReject; go Grouped; } on CommitReject { if sameNegotiation go Grouped; } // Accept is Phase1Commit on Accept { do trustThisKey; go HandshakingGroupedPhase1; } on GroupTrustThisKey { if fromGroupMember { // double check do trustThisKey; if sameNegotiation go Grouped; } } on GroupKeysUpdate { if fromGroupMember // double check do saveGroupKeys; } } state HandshakingGroupedPhase1 { on Init { send GroupTrustThisKey; send CommitAcceptForGroup; } on Rollback { if sameNegotiation { do untrustThisKey; go Grouped; } } on CommitReject { if sameNegotiation { do untrustThisKey; go Grouped; } } on CommitAccept { if sameNegotiation { do prepareOwnKeys; send GroupKeysForNewMember; do showDeviceAccepted; go Grouped; } } on GroupTrustThisKey { if fromGroupMember // double check do trustThisKey; } on GroupKeysUpdate { if fromGroupMember // double check do saveGroupKeys; } on GroupKeysAndClose { if fromGroupMember { // double check // do not save GroupKeys as default keys; key data is // already imported go Grouped; } } } external Accept 129; external Reject 130; external Cancel 131; // beacons are always broadcasted message Beacon 2, type=broadcast, ratelimit=10, security=unencrypted { field TID challenge; auto Version version; } message NegotiationRequest 3, security=untrusted { field TID challenge; field TID response; auto Version version; field TID negotiation; field bool is_group; } message NegotiationOpen 4, security=untrusted { field TID response; auto Version version; field TID negotiation; } message Rollback 5, security=untrusted { field TID negotiation; } message CommitReject 6, security=untrusted { field TID negotiation; } message CommitAcceptOfferer 7, security=untrusted { field TID negotiation; } message CommitAcceptRequester 8, security=untrusted { field TID negotiation; } message CommitAccept 9, security=untrusted { field TID negotiation; } message CommitAcceptForGroup 10, security=untrusted { field TID negotiation; } // default: security=trusted // messages are only accepted when coming from the device group message GroupTrustThisKey 11 { field Hash key; field TID negotiation; } // trust in future message GroupKeysForNewMember 12, security=attach_own_keys_for_new_member { field IdentityList ownIdentities; } message GroupKeysAndClose 13, security=attach_own_keys_for_group { field IdentityList ownIdentities; } message OwnKeysOfferer 14, security=attach_own_keys_for_group { field IdentityList ownIdentities; } message OwnKeysRequester 15, security=attach_own_keys_for_new_member { field IdentityList ownIdentities; } // grouped handshake message NegotiationRequestGrouped 16, security=untrusted { field TID challenge; field TID response; auto Version version; field TID negotiation; field bool is_group; } message GroupHandshake 17 { field TID negotiation; field Hash key; } // update group message GroupKeysUpdate 18, security=attach_own_keys_for_group { field IdentityList ownIdentities; } // initiate unled group key reset message InitUnledGroupKeyReset 19 { } message ElectGroupKeyResetLeader 20 { field TID response; } message SynchronizeGroupKeys 21, ratelimit=60 { } // This could be part of TrustSync, but actually it does not matter. // This message will not be sent by the Sync thread. It is used by // decrypt_message() to mark a previously computed rating. It is only // valid when signed with an own key. message ReceiverRating 22, security=ignore { field Rating rating; } } fsm TrustSync 2, threshold=300 { version 1, 0; state InitState { on Init { go WaitForTrustUpdate; } } state WaitForTrustUpdate { on TrustUpdate { } } // if trust changes send an update to the device group message TrustUpdate 2 { field IdentityList trust; } // if we should know the trust status ask the device group message TrustRequest 3 { field IdentityList trust; } } fsm GroupSync 3, threshold=300 { version 1, 0; state InitState { on Init { go WaitForGroupUpdate; } } state WaitForGroupUpdate { on GroupSyncUpdate { } on RequestUpdate { } } // reflect incoming ManagedGroup messages message GroupSyncUpdate 2 { embed Distribution ManagedGroup msg; } // in case a ManagedGroup message arrives for an unknown group ask the // other devices message GroupSyncRequest 3 { field Identity groupIdentity; } } }