// 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;
|
|
}
|
|
}
|
|
}
|