Split to edge-messaging and graph-chat (#1)
* docs: graph-messaging protocol [WIP] * docs: creating and using graph-messaging connection * docs: subtitle * docs: [WIP] graph-chat protocol * docs: graph-chat establishing duplex connection * apply minor typo fixes and wording adjustments to protocols docs * rename file graph-messaging -> edge-messaging * update test graph-messaging -> edge-messaging * correction re CID * duplex connection correction * rename folder graph-messaging -> edge-messaging * add duplex connection * update edge-messaging to match graph-chat * update symbols in graph-chat * sequence diagram: creating duplex connection * fix indentation * edge-messaging: REST API, crypto, IDs, URIs * REST API endpoints summary * Rest -> REST * REST API additional requirements * adjust wordings and fix typos (#2) * update readme (#4) * update readme * send message story and diagram * edge-messaging: alternative flow of creating connection * remove old diagrams * apply minor fixes * correct readme Co-authored-by: Efim Poberezkin <efim.poberezkin@gmail.com> * graph-chat: added duplex connection types * graph-chat: comment on user profile visibility * clarify duplex diagram Co-authored-by: Efim Poberezkin <efim.poberezkin@gmail.com>
|
@ -1,58 +0,0 @@
|
|||
sequenceDiagram
|
||||
participant Alice
|
||||
participant App A as Alice's app
|
||||
participant Server A as Alice's server
|
||||
participant Server B as Bob's server
|
||||
participant App B as Bob's app
|
||||
participant Bob
|
||||
|
||||
Alice ->> App A: Alice requests app to initiate adding connection
|
||||
Note over App A: App generates:<br><AB connection key><br><A_AB server keys>
|
||||
App A ->> Server A: App registers on Alice's servers <AB public key hash> associated with <A_AB server public keys> (for recepient)
|
||||
App A ->> Server A: App subsribes to receive messages from anyone sent to <AB public key hash><br>(request is signed with <A_AB server private keys>)
|
||||
|
||||
Note over App A: App shows QR code<br>for <AB public key><br>and Alice's servers
|
||||
|
||||
App A -->> Bob: Alice shows to Bob <AB public key> and her servers as QR code on the screen<br>(Alice's public key for Bob only and servers she currently uses)
|
||||
Bob ->> App B: Bob requests app to read connection QR code
|
||||
App A -->> App B: Bob's app reads QR code with <AB public key> and Alice's current servers via the camera
|
||||
|
||||
Note over App B: App generates<br><BA connection key><br><B_BA server keys>
|
||||
|
||||
App B ->> Server B: App registers on Bob's server <BA public key hash> associated with <B_BA server public keys> (for recepient)
|
||||
App B ->> Server B: App subsribes to receive messages from anyone sent to <BA public key hash><br>(request is signed with <B_BA server private key>)
|
||||
|
||||
Note over App A, App B: Now both Alice's and Bob's apps are subscribed to receive each other's messages on their servers
|
||||
|
||||
Note over App B: App generates<br><B_AB server keys><br>for Alice's servers<br>and creates<br>connection request<br>including:<br>- <BA public key><br>- Bob's profile<br>- Bob's servers<br>- <B_AB public keys>
|
||||
App B ->> Server A: Bob's app encrypts "connection request" with <AB public key> and sends it to Alice's servers<br>{to: <AB public key hash>, message: <request encrypted with AB public key>}
|
||||
Server A ->> App A: Alice's servers send "connection request" to Alice's app<br>(as <AB public key hash> allows messages from anyone)
|
||||
App A ->> Alice: Alice's app matches <AB public key hash> with <AB public key><br>and decrypts Bob's profile with <AB private key> to show it
|
||||
Alice ->> App A: Alice identifies Bob's profile and accepts the connection<br>(Bob is now added to Alice's list of connections as "pending")
|
||||
App A ->> Server A: App registers on Alice's servers <AB public key hash> associated with <B_AB server public keys> (for sender)
|
||||
|
||||
Note over Server A, Bob: Now only Bob's app can send messages for <AB public key hash> to Alice's servers<br>1) signed using <BA private key><br>2) encrypted using <AB public key><br>3) signed/encrypted using <B_AB server private/public keys>
|
||||
|
||||
Note over App A: App generates<br><A_BA server keys><br>for Bob's servers<br>and creates<br>"conn. accepted"<br>message including:<br>- Alice's profile<br>- <A_BA public keys>
|
||||
|
||||
App A ->> Server B: Alice's app signs "connection accepted" with <AB private key>, encrypts it with <BA public key> and sends it to Bob's servers<br>{to: <BA public key hash>, message: <encrypted with BA public key>}
|
||||
Server B ->> App B: Bob's servers send the message to Bob's app
|
||||
App B ->> Bob: Bob's app matches <BA public key hash> with <BA public key><br>and decrypts the Alice's profile with Bob's <BA private key>
|
||||
App B ->> Bob: Bob's app shows Alice's profile and adds it to the connections<br>(as pending)
|
||||
App B ->> Server B: App registers on Bob's servers <BA public key hash> associated with <A_BA server public keys> (for sender)
|
||||
|
||||
Note over Alice, Server B: Now only Alice's app can send messages for <BA public key hash> to Bob's servers<br>1) signed using <AB private key><br>2) encrypted using <BA public key><br>3) signed/encrypted using <A_BA server private/public keys>
|
||||
|
||||
Note over App B: App creates<br>"connection<br>acknowledged"
|
||||
|
||||
App B ->> Server A: Bob's app signs "connection acknowledged" with <BA private key>, encrypts it with <AB public key> and sends it to Alices's servers<br>{to: <AB public key hash>, message: <encrypted with AB public key>} (also signed/encrypted with B_AB server private/public keys)
|
||||
Server A ->> App A: Alice's servers send to app "connection acknowledged"
|
||||
App A ->> Alice: Bob is now in the Alice's list of connections as "established"
|
||||
|
||||
Note over App A: App creates<br>"connection<br>acknowledged"
|
||||
|
||||
App A ->> Server B: Alice's app signs "connection acknowledged" with <AB private key>, encrypts it with <BA public key> and sends it to Bob's servers<br>{to: <BA public key hash>, message: <encrypted with BA public key>} (also signed/encrypted with A_BA server private/public keys)
|
||||
Server B ->> App B: Bob's servers send to app "connection acknowledged"
|
||||
App B ->> Bob: Alice is now in the Bob's list of connections as "established"
|
||||
|
||||
Note over Alice, Bob: Now Alice and Bob can exchange messages with each other, including key rotations and changes of servers they use<br>- Alice can send messages to Bob's servers to <BA public key hash> (Bob's servers allow Alice to send messages relying on A_BA keys)<br>- Bob can subscribe to messages on his servers sent to <BA public key hash> (Bob's servers allow Bob to receive messages relying on B_BA keys)<br><br>- Bob can send messages to Alice's servers to <AB public key hash> (Alice's servers allow Bob to send messages relying on B_AB keys)<br>- Alice can subscribe to messages on her servers sent to <AB public key hash> (ALice's servers allow Alice to receive messages relying on A_AB keys)
|
Before Width: | Height: | Size: 32 KiB |
16
diagrams/edge-messaging/edge-creating-alt.mmd
Normal file
|
@ -0,0 +1,16 @@
|
|||
sequenceDiagram
|
||||
participant B as Bob (sender)
|
||||
participant S as server (conn. ID)
|
||||
participant A as Alice (recipient)
|
||||
|
||||
note over B: out-of-band msg <br> ("public" key SK <br> to sign requests)
|
||||
B -->> A: 1. send out-of-band message to Alice
|
||||
|
||||
note over A: creating connection: <br> - connection ID <br> - key RK to retrieve <br> - key SK to send
|
||||
A ->> S: 2. create secure connection
|
||||
S ->> A: respond with connection URIs
|
||||
|
||||
note over A: out-of-band msg <br> - sender conn. URI <br> - key EK to encrypt
|
||||
A -->> B: 3. send out-of-band message to Bob
|
||||
|
||||
note over S: 4. secure <br> connection ID <br> is established!
|
505
diagrams/edge-messaging/edge-creating-alt.svg
Normal file
After Width: | Height: | Size: 17 KiB |
20
diagrams/edge-messaging/edge-creating.mmd
Normal file
|
@ -0,0 +1,20 @@
|
|||
sequenceDiagram
|
||||
participant B as Bob (sender)
|
||||
participant S as server (conn. ID)
|
||||
participant A as Alice (recipient)
|
||||
|
||||
note over A: creating connection <br> (connection ID and <br> "public" key RK <br> for msg retrieval)
|
||||
A ->> S: 1. create connection
|
||||
S ->> A: respond with connection URIs
|
||||
|
||||
note over A: out-of-band msg <br> (sender conn. URI <br> and "public" key EK <br> to encrypt msgs)
|
||||
A -->> B: 2. send out-of-band message
|
||||
|
||||
note over B: accept connection <br> ("public" key SK for <br> sending messages <br> and any optional <br> info encrypted with <br> "public" key EK)
|
||||
B ->> S: 3. confirm connection (req not signed)
|
||||
|
||||
S ->> A: 4. retrieve Bob's message (RK-signed req)
|
||||
note over A: decrypt message <br> ("private" key EK)
|
||||
A ->> S: 5. secure connection
|
||||
|
||||
note over S: 6. secure <br> connection ID <br> is established!
|
505
diagrams/edge-messaging/edge-creating.svg
Normal file
After Width: | Height: | Size: 18 KiB |
10
diagrams/edge-messaging/edge-using.mmd
Normal file
|
@ -0,0 +1,10 @@
|
|||
sequenceDiagram
|
||||
participant B as Bob (sender)
|
||||
participant S as server (conn. ID)
|
||||
participant A as Alice (recipient)
|
||||
|
||||
note over B: encrypt message <br> ("public" key EK)
|
||||
B ->> S: 1. send message (SK-signed req)
|
||||
|
||||
S ->> A: 2. retrieve messages (RK-signed req)
|
||||
note over A: decrypt message <br> ("private" key EK)
|
505
diagrams/edge-messaging/edge-using.svg
Normal file
|
@ -0,0 +1,505 @@
|
|||
<svg id="mermaid-1579371509656" width="100%" xmlns="http://www.w3.org/2000/svg" height="100%" style="max-width:650px;" viewBox="-50 -10 650 355"><style>
|
||||
|
||||
|
||||
|
||||
#mermaid-1579371509656 .label {
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family);
|
||||
color: #333; }
|
||||
|
||||
#mermaid-1579371509656 .label text {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1579371509656 .node rect,
|
||||
#mermaid-1579371509656 .node circle,
|
||||
#mermaid-1579371509656 .node ellipse,
|
||||
#mermaid-1579371509656 .node polygon,
|
||||
#mermaid-1579371509656 .node path {
|
||||
fill: #ECECFF;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1px; }
|
||||
|
||||
#mermaid-1579371509656 .node .label {
|
||||
text-align: center; }
|
||||
|
||||
#mermaid-1579371509656 .node.clickable {
|
||||
cursor: pointer; }
|
||||
|
||||
#mermaid-1579371509656 .arrowheadPath {
|
||||
fill: #333333; }
|
||||
|
||||
#mermaid-1579371509656 .edgePath .path {
|
||||
stroke: #333333;
|
||||
stroke-width: 1.5px; }
|
||||
|
||||
#mermaid-1579371509656 .edgeLabel {
|
||||
background-color: #e8e8e8;
|
||||
text-align: center; }
|
||||
|
||||
#mermaid-1579371509656 .cluster rect {
|
||||
fill: #ffffde;
|
||||
stroke: #aaaa33;
|
||||
stroke-width: 1px; }
|
||||
|
||||
#mermaid-1579371509656 .cluster text {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1579371509656 div.mermaidTooltip {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
max-width: 200px;
|
||||
padding: 2px;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family);
|
||||
font-size: 12px;
|
||||
background: #ffffde;
|
||||
border: 1px solid #aaaa33;
|
||||
border-radius: 2px;
|
||||
pointer-events: none;
|
||||
z-index: 100; }
|
||||
|
||||
#mermaid-1579371509656 .actor {
|
||||
stroke: #CCCCFF;
|
||||
fill: #ECECFF; }
|
||||
|
||||
#mermaid-1579371509656 text.actor {
|
||||
fill: black;
|
||||
stroke: none; }
|
||||
|
||||
#mermaid-1579371509656 .actor-line {
|
||||
stroke: grey; }
|
||||
|
||||
#mermaid-1579371509656 .messageLine0 {
|
||||
stroke-width: 1.5;
|
||||
stroke-dasharray: '2 2';
|
||||
stroke: #333; }
|
||||
|
||||
#mermaid-1579371509656 .messageLine1 {
|
||||
stroke-width: 1.5;
|
||||
stroke-dasharray: '2 2';
|
||||
stroke: #333; }
|
||||
|
||||
#mermaid-1579371509656 #arrowhead {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1579371509656 .sequenceNumber {
|
||||
fill: white; }
|
||||
|
||||
#mermaid-1579371509656 #sequencenumber {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1579371509656 #crosshead path {
|
||||
fill: #333 !important;
|
||||
stroke: #333 !important; }
|
||||
|
||||
#mermaid-1579371509656 .messageText {
|
||||
fill: #333;
|
||||
stroke: none; }
|
||||
|
||||
#mermaid-1579371509656 .labelBox {
|
||||
stroke: #CCCCFF;
|
||||
fill: #ECECFF; }
|
||||
|
||||
#mermaid-1579371509656 .labelText {
|
||||
fill: black;
|
||||
stroke: none; }
|
||||
|
||||
#mermaid-1579371509656 .loopText {
|
||||
fill: black;
|
||||
stroke: none; }
|
||||
|
||||
#mermaid-1579371509656 .loopLine {
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: '2 2';
|
||||
stroke: #CCCCFF; }
|
||||
|
||||
#mermaid-1579371509656 .note {
|
||||
stroke: #aaaa33;
|
||||
fill: #fff5ad; }
|
||||
|
||||
#mermaid-1579371509656 .noteText {
|
||||
fill: black;
|
||||
stroke: none;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family);
|
||||
font-size: 14px; }
|
||||
|
||||
#mermaid-1579371509656 .activation0 {
|
||||
fill: #f4f4f4;
|
||||
stroke: #666; }
|
||||
|
||||
#mermaid-1579371509656 .activation1 {
|
||||
fill: #f4f4f4;
|
||||
stroke: #666; }
|
||||
|
||||
#mermaid-1579371509656 .activation2 {
|
||||
fill: #f4f4f4;
|
||||
stroke: #666; }
|
||||
|
||||
|
||||
#mermaid-1579371509656 .mermaid-main-font {
|
||||
font-family: "trebuchet ms", verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1579371509656 .section {
|
||||
stroke: none;
|
||||
opacity: 0.2; }
|
||||
|
||||
#mermaid-1579371509656 .section0 {
|
||||
fill: rgba(102, 102, 255, 0.49); }
|
||||
|
||||
#mermaid-1579371509656 .section2 {
|
||||
fill: #fff400; }
|
||||
|
||||
#mermaid-1579371509656 .section1,
|
||||
#mermaid-1579371509656 .section3 {
|
||||
fill: white;
|
||||
opacity: 0.2; }
|
||||
|
||||
#mermaid-1579371509656 .sectionTitle0 {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1579371509656 .sectionTitle1 {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1579371509656 .sectionTitle2 {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1579371509656 .sectionTitle3 {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1579371509656 .sectionTitle {
|
||||
text-anchor: start;
|
||||
font-size: 11px;
|
||||
text-height: 14px;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
|
||||
#mermaid-1579371509656 .grid .tick {
|
||||
stroke: lightgrey;
|
||||
opacity: 0.8;
|
||||
shape-rendering: crispEdges; }
|
||||
#mermaid-1579371509656 .grid .tick text {
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1579371509656 .grid path {
|
||||
stroke-width: 0; }
|
||||
|
||||
|
||||
#mermaid-1579371509656 .today {
|
||||
fill: none;
|
||||
stroke: red;
|
||||
stroke-width: 2px; }
|
||||
|
||||
|
||||
|
||||
#mermaid-1579371509656 .task {
|
||||
stroke-width: 2; }
|
||||
|
||||
#mermaid-1579371509656 .taskText {
|
||||
text-anchor: middle;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1579371509656 .taskText:not([font-size]) {
|
||||
font-size: 11px; }
|
||||
|
||||
#mermaid-1579371509656 .taskTextOutsideRight {
|
||||
fill: black;
|
||||
text-anchor: start;
|
||||
font-size: 11px;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1579371509656 .taskTextOutsideLeft {
|
||||
fill: black;
|
||||
text-anchor: end;
|
||||
font-size: 11px; }
|
||||
|
||||
|
||||
#mermaid-1579371509656 .task.clickable {
|
||||
cursor: pointer; }
|
||||
|
||||
#mermaid-1579371509656 .taskText.clickable {
|
||||
cursor: pointer;
|
||||
fill: #003163 !important;
|
||||
font-weight: bold; }
|
||||
|
||||
#mermaid-1579371509656 .taskTextOutsideLeft.clickable {
|
||||
cursor: pointer;
|
||||
fill: #003163 !important;
|
||||
font-weight: bold; }
|
||||
|
||||
#mermaid-1579371509656 .taskTextOutsideRight.clickable {
|
||||
cursor: pointer;
|
||||
fill: #003163 !important;
|
||||
font-weight: bold; }
|
||||
|
||||
|
||||
#mermaid-1579371509656 .taskText0,
|
||||
#mermaid-1579371509656 .taskText1,
|
||||
#mermaid-1579371509656 .taskText2,
|
||||
#mermaid-1579371509656 .taskText3 {
|
||||
fill: white; }
|
||||
|
||||
#mermaid-1579371509656 .task0,
|
||||
#mermaid-1579371509656 .task1,
|
||||
#mermaid-1579371509656 .task2,
|
||||
#mermaid-1579371509656 .task3 {
|
||||
fill: #8a90dd;
|
||||
stroke: #534fbc; }
|
||||
|
||||
#mermaid-1579371509656 .taskTextOutside0,
|
||||
#mermaid-1579371509656 .taskTextOutside2 {
|
||||
fill: black; }
|
||||
|
||||
#mermaid-1579371509656 .taskTextOutside1,
|
||||
#mermaid-1579371509656 .taskTextOutside3 {
|
||||
fill: black; }
|
||||
|
||||
|
||||
#mermaid-1579371509656 .active0,
|
||||
#mermaid-1579371509656 .active1,
|
||||
#mermaid-1579371509656 .active2,
|
||||
#mermaid-1579371509656 .active3 {
|
||||
fill: #bfc7ff;
|
||||
stroke: #534fbc; }
|
||||
|
||||
#mermaid-1579371509656 .activeText0,
|
||||
#mermaid-1579371509656 .activeText1,
|
||||
#mermaid-1579371509656 .activeText2,
|
||||
#mermaid-1579371509656 .activeText3 {
|
||||
fill: black !important; }
|
||||
|
||||
|
||||
#mermaid-1579371509656 .done0,
|
||||
#mermaid-1579371509656 .done1,
|
||||
#mermaid-1579371509656 .done2,
|
||||
#mermaid-1579371509656 .done3 {
|
||||
stroke: grey;
|
||||
fill: lightgrey;
|
||||
stroke-width: 2; }
|
||||
|
||||
#mermaid-1579371509656 .doneText0,
|
||||
#mermaid-1579371509656 .doneText1,
|
||||
#mermaid-1579371509656 .doneText2,
|
||||
#mermaid-1579371509656 .doneText3 {
|
||||
fill: black !important; }
|
||||
|
||||
|
||||
#mermaid-1579371509656 .crit0,
|
||||
#mermaid-1579371509656 .crit1,
|
||||
#mermaid-1579371509656 .crit2,
|
||||
#mermaid-1579371509656 .crit3 {
|
||||
stroke: #ff8888;
|
||||
fill: red;
|
||||
stroke-width: 2; }
|
||||
|
||||
#mermaid-1579371509656 .activeCrit0,
|
||||
#mermaid-1579371509656 .activeCrit1,
|
||||
#mermaid-1579371509656 .activeCrit2,
|
||||
#mermaid-1579371509656 .activeCrit3 {
|
||||
stroke: #ff8888;
|
||||
fill: #bfc7ff;
|
||||
stroke-width: 2; }
|
||||
|
||||
#mermaid-1579371509656 .doneCrit0,
|
||||
#mermaid-1579371509656 .doneCrit1,
|
||||
#mermaid-1579371509656 .doneCrit2,
|
||||
#mermaid-1579371509656 .doneCrit3 {
|
||||
stroke: #ff8888;
|
||||
fill: lightgrey;
|
||||
stroke-width: 2;
|
||||
cursor: pointer;
|
||||
shape-rendering: crispEdges; }
|
||||
|
||||
#mermaid-1579371509656 .milestone {
|
||||
transform: rotate(45deg) scale(0.8, 0.8); }
|
||||
|
||||
#mermaid-1579371509656 .milestoneText {
|
||||
font-style: italic; }
|
||||
|
||||
#mermaid-1579371509656 .doneCritText0,
|
||||
#mermaid-1579371509656 .doneCritText1,
|
||||
#mermaid-1579371509656 .doneCritText2,
|
||||
#mermaid-1579371509656 .doneCritText3 {
|
||||
fill: black !important; }
|
||||
|
||||
#mermaid-1579371509656 .activeCritText0,
|
||||
#mermaid-1579371509656 .activeCritText1,
|
||||
#mermaid-1579371509656 .activeCritText2,
|
||||
#mermaid-1579371509656 .activeCritText3 {
|
||||
fill: black !important; }
|
||||
|
||||
#mermaid-1579371509656 .titleText {
|
||||
text-anchor: middle;
|
||||
font-size: 18px;
|
||||
fill: black;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1579371509656 g.classGroup text {
|
||||
fill: #9370DB;
|
||||
stroke: none;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family);
|
||||
font-size: 10px; }
|
||||
#mermaid-1579371509656 g.classGroup text .title {
|
||||
font-weight: bolder; }
|
||||
|
||||
#mermaid-1579371509656 g.clickable {
|
||||
cursor: pointer; }
|
||||
|
||||
#mermaid-1579371509656 g.classGroup rect {
|
||||
fill: #ECECFF;
|
||||
stroke: #9370DB; }
|
||||
|
||||
#mermaid-1579371509656 g.classGroup line {
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1579371509656 .classLabel .box {
|
||||
stroke: none;
|
||||
stroke-width: 0;
|
||||
fill: #ECECFF;
|
||||
opacity: 0.5; }
|
||||
|
||||
#mermaid-1579371509656 .classLabel .label {
|
||||
fill: #9370DB;
|
||||
font-size: 10px; }
|
||||
|
||||
#mermaid-1579371509656 .relation {
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1;
|
||||
fill: none; }
|
||||
|
||||
#mermaid-1579371509656 .dashed-line {
|
||||
stroke-dasharray: 3; }
|
||||
|
||||
#mermaid-1579371509656 #compositionStart {
|
||||
fill: #9370DB;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1579371509656 #compositionEnd {
|
||||
fill: #9370DB;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1579371509656 #aggregationStart {
|
||||
fill: #ECECFF;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1579371509656 #aggregationEnd {
|
||||
fill: #ECECFF;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1579371509656 #dependencyStart {
|
||||
fill: #9370DB;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1579371509656 #dependencyEnd {
|
||||
fill: #9370DB;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1579371509656 #extensionStart {
|
||||
fill: #9370DB;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1579371509656 #extensionEnd {
|
||||
fill: #9370DB;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1579371509656 .commit-id,
|
||||
#mermaid-1579371509656 .commit-msg,
|
||||
#mermaid-1579371509656 .branch-label {
|
||||
fill: lightgrey;
|
||||
color: lightgrey;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1579371509656 .pieTitleText {
|
||||
text-anchor: middle;
|
||||
font-size: 25px;
|
||||
fill: black;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1579371509656 .slice {
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1579371509656 g.stateGroup text {
|
||||
fill: #9370DB;
|
||||
stroke: none;
|
||||
font-size: 10px;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1579371509656 g.stateGroup text {
|
||||
fill: #9370DB;
|
||||
stroke: none;
|
||||
font-size: 10px; }
|
||||
|
||||
#mermaid-1579371509656 g.stateGroup .state-title {
|
||||
font-weight: bolder;
|
||||
fill: black; }
|
||||
|
||||
#mermaid-1579371509656 g.stateGroup rect {
|
||||
fill: #ECECFF;
|
||||
stroke: #9370DB; }
|
||||
|
||||
#mermaid-1579371509656 g.stateGroup line {
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1579371509656 .transition {
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1;
|
||||
fill: none; }
|
||||
|
||||
#mermaid-1579371509656 .stateGroup .composit {
|
||||
fill: white;
|
||||
border-bottom: 1px; }
|
||||
|
||||
#mermaid-1579371509656 .stateGroup .alt-composit {
|
||||
fill: #e0e0e0;
|
||||
border-bottom: 1px; }
|
||||
|
||||
#mermaid-1579371509656 .state-note {
|
||||
stroke: #aaaa33;
|
||||
fill: #fff5ad; }
|
||||
#mermaid-1579371509656 .state-note text {
|
||||
fill: black;
|
||||
stroke: none;
|
||||
font-size: 10px; }
|
||||
|
||||
#mermaid-1579371509656 .stateLabel .box {
|
||||
stroke: none;
|
||||
stroke-width: 0;
|
||||
fill: #ECECFF;
|
||||
opacity: 0.5; }
|
||||
|
||||
#mermaid-1579371509656 .stateLabel text {
|
||||
fill: black;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
:root {
|
||||
--mermaid-font-family: '"trebuchet ms", verdana, arial';
|
||||
--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive; }
|
||||
|
||||
:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}</style><style>#mermaid-1579371509656 {
|
||||
color: rgb(0, 0, 0);
|
||||
font: normal normal 400 normal 16px / normal "trebuchet ms", verdana, arial;
|
||||
}</style><g></g><g><line id="actor1692" x1="75" y1="5" x2="75" y2="344" class="actor-line" stroke-width="0.5px" stroke="#999"></line><rect x="0" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="75" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px;"><tspan x="75" dy="0">Bob (sender)</tspan></text></g><g><line id="actor1693" x1="275" y1="5" x2="275" y2="344" class="actor-line" stroke-width="0.5px" stroke="#999"></line><rect x="200" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="275" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px;"><tspan x="275" dy="0">server (conn. ID)</tspan></text></g><g><line id="actor1694" x1="475" y1="5" x2="475" y2="344" class="actor-line" stroke-width="0.5px" stroke="#999"></line><rect x="400" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="475" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px;"><tspan x="475" dy="0">Alice (recipient)</tspan></text></g><defs><marker id="arrowhead" refX="5" refY="2" markerWidth="6" markerHeight="4" orient="auto"><path d="M 0,0 V 4 L6,2 Z"></path></marker></defs><defs><marker id="crosshead" markerWidth="15" markerHeight="8" orient="auto" refX="16" refY="4"><path fill="black" stroke="#000000" stroke-width="1px" d="M 9,2 V 6 L16,4 Z" style="stroke-dasharray: 0, 0;"></path><path fill="none" stroke="#000000" stroke-width="1px" d="M 0,1 L 6,7 M 6,1 L 0,7" style="stroke-dasharray: 0, 0;"></path></marker></defs><defs><marker id="sequencenumber" refX="15" refY="15" markerWidth="60" markerHeight="40" orient="auto"><circle cx="15" cy="15" r="6"></circle></marker></defs><g><rect x="0" y="75" fill="#EDF2AE" stroke="#666" width="150" height="52" rx="0" ry="0" class="note"></rect><text x="-4" y="99" class="noteText"><tspan x="16">encrypt message </tspan></text><text x="-4" y="115" class="noteText"><tspan x="16"> ("public" key EK)</tspan></text></g><g><text x="175" y="155" class="messageText" style="text-anchor: middle;">1. send message (SK-signed req)</text><line x1="75" y1="162" x2="275" y2="162" class="messageLine0" stroke-width="2" stroke="black" marker-end="url(#arrowhead)" style="fill: none;"></line></g><g><text x="375" y="190" class="messageText" style="text-anchor: middle;">2. retrieve messages (RK-signed req)</text><line x1="275" y1="197" x2="475" y2="197" class="messageLine0" stroke-width="2" stroke="black" marker-end="url(#arrowhead)" style="fill: none;"></line></g><g><rect x="400" y="207" fill="#EDF2AE" stroke="#666" width="150" height="52" rx="0" ry="0" class="note"></rect><text x="396" y="231" class="noteText"><tspan x="416">decrypt message </tspan></text><text x="396" y="247" class="noteText"><tspan x="416"> ("private" key EK)</tspan></text></g><g><rect x="0" y="279" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="75" y="311.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px;"><tspan x="75" dy="0">Bob (sender)</tspan></text></g><g><rect x="200" y="279" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="275" y="311.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px;"><tspan x="275" dy="0">server (conn. ID)</tspan></text></g><g><rect x="400" y="279" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="475" y="311.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px;"><tspan x="475" dy="0">Alice (recipient)</tspan></text></g></svg>
|
After Width: | Height: | Size: 15 KiB |
11
diagrams/edge-messaging/edge.mmd
Normal file
|
@ -0,0 +1,11 @@
|
|||
graph LR
|
||||
VS{{"verify sender (SK)"}}
|
||||
VR{{"verify recipient (RK)"}}
|
||||
|
||||
S(sender) -->|msg| VS
|
||||
subgraph "server (connection ID)"
|
||||
VS --> DB[("storage")]
|
||||
DB --> VR
|
||||
end
|
||||
R(recipient) -->|"1) req"| VR
|
||||
VR -->|"2) msg"| R
|
505
diagrams/edge-messaging/edge.svg
Normal file
After Width: | Height: | Size: 19 KiB |
40
diagrams/graph-chat/duplex-creating.mmd
Normal file
|
@ -0,0 +1,40 @@
|
|||
sequenceDiagram
|
||||
participant A as Alice's app
|
||||
participant SA as Alice's servers <br> (connections CAi)
|
||||
participant SB as Bob's servers <br> (connections CBi)
|
||||
participant B as Bob's app
|
||||
|
||||
note over A, SA: 1. initiate duplex connection
|
||||
A ->> SA: create n connections CAi (send IDAi and RKAi)
|
||||
SA ->> A: receive connections URIs (RUAi and SUAi)
|
||||
A ->> SA: subscribe to messages from CAi
|
||||
|
||||
A -->> B: 2. send secure message with connections "public" keys (EKAi) and URIs (SUAi)
|
||||
|
||||
note over B: 3. accept Alice's <br> duplex connection
|
||||
B ->> SB: create n connections CBi (send IDBi and RKBi)
|
||||
SB ->> B: receive connections URIs (RUBi and SUBi)
|
||||
B ->> SB: subscribe to messages from CBi
|
||||
note over B: prepare msgs with: <br> - sender key SKAi <br> - Bob's profile <br> - EKBi and SUBi
|
||||
B ->> SA: accept CAi (send EKAi-encrypted messages to CAi)
|
||||
|
||||
note over A: 4. Add Bob's duplex <br> connection
|
||||
SA ->> A: receive Bob's app messages
|
||||
note over A: identify Bob and <br> confirm connection
|
||||
A ->> SA: secure connections CAi with Bob's SKAi
|
||||
note over A: prepare msgs with: <br> - sender key SKBi <br> - Alice's profile <br> - CAi confirmation
|
||||
A ->> SB: accept CBi (send EKBi-encrypted messages to CBi)
|
||||
note over A: Bob's duplex conn. <br> "pending"
|
||||
|
||||
note over B: 5. Add Alice's duplex <br> connection
|
||||
SB ->> B: receive Alice's app messages
|
||||
B ->> SB: secure connections CBi with Alice's SKBi
|
||||
B ->> SA: send "welcome" message to duplex connection (via all CAi)
|
||||
note over B: Alice's duplex conn. <br> "pending"
|
||||
|
||||
SA ->> A: 6. receive Bob's "welcome" message
|
||||
note over A: Bob's duplex conn. <br> "established"
|
||||
A ->> SB: send "welcome" message to duplex connection (via all CBi)
|
||||
|
||||
SB ->> B: 7. receive Alice's "welcome" message
|
||||
note over B: Alice's duplex conn. <br> "established"
|
505
diagrams/graph-chat/duplex-creating.svg
Normal file
After Width: | Height: | Size: 24 KiB |
21
diagrams/graph-chat/duplex-using.mmd
Normal file
|
@ -0,0 +1,21 @@
|
|||
sequenceDiagram
|
||||
participant A as Alice's app
|
||||
participant SA as Alice's servers <br> (connections CAi)
|
||||
participant SB as Bob's servers <br> (connections CBi)
|
||||
participant B as Bob's app
|
||||
|
||||
note over A: 1. Alice writes msg <br> to Bob in the app: <br> - message ID <br> - timestamp <br> - message body
|
||||
note over A: sign and encrypt <br> message version <br> for each conn. CBi
|
||||
A ->> SB: send message versions to connections CBi on Bob's servers
|
||||
|
||||
SB ->> B: 2. retrive message versions from CBi
|
||||
note over B: decrypt and verify <br> message versions
|
||||
note over B: discard duplicates <br> and show in chat <br> with Alice
|
||||
|
||||
note over B: 3. prepare msg <br> "message received": <br> - new message ID <br> - msg correlation ID <br> - receipt timestamp
|
||||
note over B: sign and encrypt <br> message version <br> for each conn. CAi
|
||||
B ->> SA: send message versions to connections CAi on Alice's servers
|
||||
|
||||
SA ->> A: 4. retrive message versions from CAi
|
||||
note over A: decrypt and verify <br> message versions
|
||||
note over A: discard duplicates <br> and show sent msg <br> as delivered <br> in chat with Bob
|
505
diagrams/graph-chat/duplex-using.svg
Normal file
After Width: | Height: | Size: 20 KiB |
|
@ -1,17 +0,0 @@
|
|||
sequenceDiagram
|
||||
participant Alice
|
||||
participant App A as Alice's app
|
||||
participant Server A as Alice's server
|
||||
participant Server B as Bob's server
|
||||
participant App B as Bob's app
|
||||
participant Bob
|
||||
Alice -->> App A: Alice writes the message to Bob in the app
|
||||
App A ->> Server B: The message (including msg ID & timestamp) is signed with <AB private key>, encrypted with Bob's <BA public key> and sent to Bob's servers<br>{to: <BA public key hash>, message: <encrypted with BA public key>} (also signed/encrypted with <A_BA server private/public keys>)
|
||||
Server B ->> App B: Bob's servers send the message to Bob's app
|
||||
App B -->> Bob: Bob's app uses <BA public key hash> to identify Alice<br>and decrypts the messages with Bob's <BA private key>
|
||||
|
||||
Note over App B: App creates<br>"message received"<br>including:<br>- msg correlation ID<br>- timestamp
|
||||
|
||||
App B ->> Server A: "message received" signed with <BA private key>, encrypted with Alice's <AB public key><br>{to: <AB public key hash>, message: <encrypted with AB public key>} (also signed/encrypted with <B_AB server private/public keys>)
|
||||
Server A ->> App A: "message received": Alice's app uses <AB public key hash> to identify Bob<br>and decrypts "message received" with Alice's <AB private key>
|
||||
App A -->> Alice: "message received" shown to Alice
|
Before Width: | Height: | Size: 20 KiB |
|
@ -1,8 +0,0 @@
|
|||
graph LR
|
||||
S("sender (key 1)") --> C
|
||||
C --> R("receiver (key 2)")
|
||||
R --> C
|
||||
subgraph connection
|
||||
C{{"verify sender (1) ---> pubsub <---> verify receiver (2)"}}
|
||||
end
|
||||
|
|
@ -1,498 +0,0 @@
|
|||
<svg id="mermaid-1577142258919" width="840.203125" xmlns="http://www.w3.org/2000/svg" height="127.75" viewBox="0 0 840.203125 127.75"><style>
|
||||
|
||||
|
||||
|
||||
#mermaid-1577142258919 .label {
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family);
|
||||
color: #333; }
|
||||
|
||||
#mermaid-1577142258919 .label text {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1577142258919 .node rect,
|
||||
#mermaid-1577142258919 .node circle,
|
||||
#mermaid-1577142258919 .node ellipse,
|
||||
#mermaid-1577142258919 .node polygon {
|
||||
fill: #ECECFF;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1px; }
|
||||
|
||||
#mermaid-1577142258919 .node .label {
|
||||
text-align: center; }
|
||||
|
||||
#mermaid-1577142258919 .node.clickable {
|
||||
cursor: pointer; }
|
||||
|
||||
#mermaid-1577142258919 .arrowheadPath {
|
||||
fill: #333333; }
|
||||
|
||||
#mermaid-1577142258919 .edgePath .path {
|
||||
stroke: #333333;
|
||||
stroke-width: 1.5px; }
|
||||
|
||||
#mermaid-1577142258919 .edgeLabel {
|
||||
background-color: #e8e8e8;
|
||||
text-align: center; }
|
||||
|
||||
#mermaid-1577142258919 .cluster rect {
|
||||
fill: #ffffde;
|
||||
stroke: #aaaa33;
|
||||
stroke-width: 1px; }
|
||||
|
||||
#mermaid-1577142258919 .cluster text {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1577142258919 div.mermaidTooltip {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
max-width: 200px;
|
||||
padding: 2px;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family);
|
||||
font-size: 12px;
|
||||
background: #ffffde;
|
||||
border: 1px solid #aaaa33;
|
||||
border-radius: 2px;
|
||||
pointer-events: none;
|
||||
z-index: 100; }
|
||||
|
||||
#mermaid-1577142258919 .actor {
|
||||
stroke: #CCCCFF;
|
||||
fill: #ECECFF; }
|
||||
|
||||
#mermaid-1577142258919 text.actor {
|
||||
fill: black;
|
||||
stroke: none; }
|
||||
|
||||
#mermaid-1577142258919 .actor-line {
|
||||
stroke: grey; }
|
||||
|
||||
#mermaid-1577142258919 .messageLine0 {
|
||||
stroke-width: 1.5;
|
||||
stroke-dasharray: '2 2';
|
||||
stroke: #333; }
|
||||
|
||||
#mermaid-1577142258919 .messageLine1 {
|
||||
stroke-width: 1.5;
|
||||
stroke-dasharray: '2 2';
|
||||
stroke: #333; }
|
||||
|
||||
#mermaid-1577142258919 #arrowhead {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1577142258919 .sequenceNumber {
|
||||
fill: white; }
|
||||
|
||||
#mermaid-1577142258919 #sequencenumber {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1577142258919 #crosshead path {
|
||||
fill: #333 !important;
|
||||
stroke: #333 !important; }
|
||||
|
||||
#mermaid-1577142258919 .messageText {
|
||||
fill: #333;
|
||||
stroke: none; }
|
||||
|
||||
#mermaid-1577142258919 .labelBox {
|
||||
stroke: #CCCCFF;
|
||||
fill: #ECECFF; }
|
||||
|
||||
#mermaid-1577142258919 .labelText {
|
||||
fill: black;
|
||||
stroke: none; }
|
||||
|
||||
#mermaid-1577142258919 .loopText {
|
||||
fill: black;
|
||||
stroke: none; }
|
||||
|
||||
#mermaid-1577142258919 .loopLine {
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: '2 2';
|
||||
stroke: #CCCCFF; }
|
||||
|
||||
#mermaid-1577142258919 .note {
|
||||
stroke: #aaaa33;
|
||||
fill: #fff5ad; }
|
||||
|
||||
#mermaid-1577142258919 .noteText {
|
||||
fill: black;
|
||||
stroke: none;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family);
|
||||
font-size: 14px; }
|
||||
|
||||
#mermaid-1577142258919 .activation0 {
|
||||
fill: #f4f4f4;
|
||||
stroke: #666; }
|
||||
|
||||
#mermaid-1577142258919 .activation1 {
|
||||
fill: #f4f4f4;
|
||||
stroke: #666; }
|
||||
|
||||
#mermaid-1577142258919 .activation2 {
|
||||
fill: #f4f4f4;
|
||||
stroke: #666; }
|
||||
|
||||
|
||||
#mermaid-1577142258919 .mermaid-main-font {
|
||||
font-family: "trebuchet ms", verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1577142258919 .section {
|
||||
stroke: none;
|
||||
opacity: 0.2; }
|
||||
|
||||
#mermaid-1577142258919 .section0 {
|
||||
fill: rgba(102, 102, 255, 0.49); }
|
||||
|
||||
#mermaid-1577142258919 .section2 {
|
||||
fill: #fff400; }
|
||||
|
||||
#mermaid-1577142258919 .section1,
|
||||
#mermaid-1577142258919 .section3 {
|
||||
fill: white;
|
||||
opacity: 0.2; }
|
||||
|
||||
#mermaid-1577142258919 .sectionTitle0 {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1577142258919 .sectionTitle1 {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1577142258919 .sectionTitle2 {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1577142258919 .sectionTitle3 {
|
||||
fill: #333; }
|
||||
|
||||
#mermaid-1577142258919 .sectionTitle {
|
||||
text-anchor: start;
|
||||
font-size: 11px;
|
||||
text-height: 14px;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
|
||||
#mermaid-1577142258919 .grid .tick {
|
||||
stroke: lightgrey;
|
||||
opacity: 0.3;
|
||||
shape-rendering: crispEdges; }
|
||||
#mermaid-1577142258919 .grid .tick text {
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1577142258919 .grid path {
|
||||
stroke-width: 0; }
|
||||
|
||||
|
||||
#mermaid-1577142258919 .today {
|
||||
fill: none;
|
||||
stroke: red;
|
||||
stroke-width: 2px; }
|
||||
|
||||
|
||||
|
||||
#mermaid-1577142258919 .task {
|
||||
stroke-width: 2; }
|
||||
|
||||
#mermaid-1577142258919 .taskText {
|
||||
text-anchor: middle;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1577142258919 .taskText:not([font-size]) {
|
||||
font-size: 11px; }
|
||||
|
||||
#mermaid-1577142258919 .taskTextOutsideRight {
|
||||
fill: black;
|
||||
text-anchor: start;
|
||||
font-size: 11px;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1577142258919 .taskTextOutsideLeft {
|
||||
fill: black;
|
||||
text-anchor: end;
|
||||
font-size: 11px; }
|
||||
|
||||
|
||||
#mermaid-1577142258919 .task.clickable {
|
||||
cursor: pointer; }
|
||||
|
||||
#mermaid-1577142258919 .taskText.clickable {
|
||||
cursor: pointer;
|
||||
fill: #003163 !important;
|
||||
font-weight: bold; }
|
||||
|
||||
#mermaid-1577142258919 .taskTextOutsideLeft.clickable {
|
||||
cursor: pointer;
|
||||
fill: #003163 !important;
|
||||
font-weight: bold; }
|
||||
|
||||
#mermaid-1577142258919 .taskTextOutsideRight.clickable {
|
||||
cursor: pointer;
|
||||
fill: #003163 !important;
|
||||
font-weight: bold; }
|
||||
|
||||
|
||||
#mermaid-1577142258919 .taskText0,
|
||||
#mermaid-1577142258919 .taskText1,
|
||||
#mermaid-1577142258919 .taskText2,
|
||||
#mermaid-1577142258919 .taskText3 {
|
||||
fill: white; }
|
||||
|
||||
#mermaid-1577142258919 .task0,
|
||||
#mermaid-1577142258919 .task1,
|
||||
#mermaid-1577142258919 .task2,
|
||||
#mermaid-1577142258919 .task3 {
|
||||
fill: #8a90dd;
|
||||
stroke: #534fbc; }
|
||||
|
||||
#mermaid-1577142258919 .taskTextOutside0,
|
||||
#mermaid-1577142258919 .taskTextOutside2 {
|
||||
fill: black; }
|
||||
|
||||
#mermaid-1577142258919 .taskTextOutside1,
|
||||
#mermaid-1577142258919 .taskTextOutside3 {
|
||||
fill: black; }
|
||||
|
||||
|
||||
#mermaid-1577142258919 .active0,
|
||||
#mermaid-1577142258919 .active1,
|
||||
#mermaid-1577142258919 .active2,
|
||||
#mermaid-1577142258919 .active3 {
|
||||
fill: #bfc7ff;
|
||||
stroke: #534fbc; }
|
||||
|
||||
#mermaid-1577142258919 .activeText0,
|
||||
#mermaid-1577142258919 .activeText1,
|
||||
#mermaid-1577142258919 .activeText2,
|
||||
#mermaid-1577142258919 .activeText3 {
|
||||
fill: black !important; }
|
||||
|
||||
|
||||
#mermaid-1577142258919 .done0,
|
||||
#mermaid-1577142258919 .done1,
|
||||
#mermaid-1577142258919 .done2,
|
||||
#mermaid-1577142258919 .done3 {
|
||||
stroke: grey;
|
||||
fill: lightgrey;
|
||||
stroke-width: 2; }
|
||||
|
||||
#mermaid-1577142258919 .doneText0,
|
||||
#mermaid-1577142258919 .doneText1,
|
||||
#mermaid-1577142258919 .doneText2,
|
||||
#mermaid-1577142258919 .doneText3 {
|
||||
fill: black !important; }
|
||||
|
||||
|
||||
#mermaid-1577142258919 .crit0,
|
||||
#mermaid-1577142258919 .crit1,
|
||||
#mermaid-1577142258919 .crit2,
|
||||
#mermaid-1577142258919 .crit3 {
|
||||
stroke: #ff8888;
|
||||
fill: red;
|
||||
stroke-width: 2; }
|
||||
|
||||
#mermaid-1577142258919 .activeCrit0,
|
||||
#mermaid-1577142258919 .activeCrit1,
|
||||
#mermaid-1577142258919 .activeCrit2,
|
||||
#mermaid-1577142258919 .activeCrit3 {
|
||||
stroke: #ff8888;
|
||||
fill: #bfc7ff;
|
||||
stroke-width: 2; }
|
||||
|
||||
#mermaid-1577142258919 .doneCrit0,
|
||||
#mermaid-1577142258919 .doneCrit1,
|
||||
#mermaid-1577142258919 .doneCrit2,
|
||||
#mermaid-1577142258919 .doneCrit3 {
|
||||
stroke: #ff8888;
|
||||
fill: lightgrey;
|
||||
stroke-width: 2;
|
||||
cursor: pointer;
|
||||
shape-rendering: crispEdges; }
|
||||
|
||||
#mermaid-1577142258919 .milestone {
|
||||
transform: rotate(45deg) scale(0.8, 0.8); }
|
||||
|
||||
#mermaid-1577142258919 .milestoneText {
|
||||
font-style: italic; }
|
||||
|
||||
#mermaid-1577142258919 .doneCritText0,
|
||||
#mermaid-1577142258919 .doneCritText1,
|
||||
#mermaid-1577142258919 .doneCritText2,
|
||||
#mermaid-1577142258919 .doneCritText3 {
|
||||
fill: black !important; }
|
||||
|
||||
#mermaid-1577142258919 .activeCritText0,
|
||||
#mermaid-1577142258919 .activeCritText1,
|
||||
#mermaid-1577142258919 .activeCritText2,
|
||||
#mermaid-1577142258919 .activeCritText3 {
|
||||
fill: black !important; }
|
||||
|
||||
#mermaid-1577142258919 .titleText {
|
||||
text-anchor: middle;
|
||||
font-size: 18px;
|
||||
fill: black;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1577142258919 g.classGroup text {
|
||||
fill: #9370DB;
|
||||
stroke: none;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family);
|
||||
font-size: 10px; }
|
||||
#mermaid-1577142258919 g.classGroup text .title {
|
||||
font-weight: bolder; }
|
||||
|
||||
#mermaid-1577142258919 g.classGroup rect {
|
||||
fill: #ECECFF;
|
||||
stroke: #9370DB; }
|
||||
|
||||
#mermaid-1577142258919 g.classGroup line {
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1577142258919 .classLabel .box {
|
||||
stroke: none;
|
||||
stroke-width: 0;
|
||||
fill: #ECECFF;
|
||||
opacity: 0.5; }
|
||||
|
||||
#mermaid-1577142258919 .classLabel .label {
|
||||
fill: #9370DB;
|
||||
font-size: 10px; }
|
||||
|
||||
#mermaid-1577142258919 .relation {
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1;
|
||||
fill: none; }
|
||||
|
||||
#mermaid-1577142258919 #compositionStart {
|
||||
fill: #9370DB;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1577142258919 #compositionEnd {
|
||||
fill: #9370DB;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1577142258919 #aggregationStart {
|
||||
fill: #ECECFF;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1577142258919 #aggregationEnd {
|
||||
fill: #ECECFF;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1577142258919 #dependencyStart {
|
||||
fill: #9370DB;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1577142258919 #dependencyEnd {
|
||||
fill: #9370DB;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1577142258919 #extensionStart {
|
||||
fill: #9370DB;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1577142258919 #extensionEnd {
|
||||
fill: #9370DB;
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1577142258919 .commit-id,
|
||||
#mermaid-1577142258919 .commit-msg,
|
||||
#mermaid-1577142258919 .branch-label {
|
||||
fill: lightgrey;
|
||||
color: lightgrey;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1577142258919 .pieTitleText {
|
||||
text-anchor: middle;
|
||||
font-size: 25px;
|
||||
fill: black;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1577142258919 .slice {
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1577142258919 g.stateGroup text {
|
||||
fill: #9370DB;
|
||||
stroke: none;
|
||||
font-size: 10px;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
#mermaid-1577142258919 g.stateGroup text {
|
||||
fill: #9370DB;
|
||||
stroke: none;
|
||||
font-size: 10px; }
|
||||
|
||||
#mermaid-1577142258919 g.stateGroup .state-title {
|
||||
font-weight: bolder;
|
||||
fill: black; }
|
||||
|
||||
#mermaid-1577142258919 g.stateGroup rect {
|
||||
fill: #ECECFF;
|
||||
stroke: #9370DB; }
|
||||
|
||||
#mermaid-1577142258919 g.stateGroup line {
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1; }
|
||||
|
||||
#mermaid-1577142258919 .transition {
|
||||
stroke: #9370DB;
|
||||
stroke-width: 1;
|
||||
fill: none; }
|
||||
|
||||
#mermaid-1577142258919 .stateGroup .composit {
|
||||
fill: white;
|
||||
border-bottom: 1px; }
|
||||
|
||||
#mermaid-1577142258919 .stateGroup .alt-composit {
|
||||
fill: #e0e0e0;
|
||||
border-bottom: 1px; }
|
||||
|
||||
#mermaid-1577142258919 .state-note {
|
||||
stroke: #aaaa33;
|
||||
fill: #fff5ad; }
|
||||
#mermaid-1577142258919 .state-note text {
|
||||
fill: black;
|
||||
stroke: none;
|
||||
font-size: 10px; }
|
||||
|
||||
#mermaid-1577142258919 .stateLabel .box {
|
||||
stroke: none;
|
||||
stroke-width: 0;
|
||||
fill: #ECECFF;
|
||||
opacity: 0.5; }
|
||||
|
||||
#mermaid-1577142258919 .stateLabel text {
|
||||
fill: black;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-family: var(--mermaid-font-family); }
|
||||
|
||||
:root {
|
||||
--mermaid-font-family: '"trebuchet ms", verdana, arial';
|
||||
--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive; }
|
||||
|
||||
:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}</style><style>#mermaid-1577142258919 {
|
||||
color: rgb(0, 0, 0);
|
||||
font: normal normal 400 normal 16px / normal "trebuchet ms", verdana, arial;
|
||||
}</style><g transform="translate(0, 0)"><g class="output"><g class="clusters"><g class="cluster" id="connection" transform="translate(414.5390625,63.875)" style="opacity: 1;"><rect width="468.515625" height="111.75" x="-234.2578125" y="-55.875"></rect><g class="label" transform="translate(0, -41.875)" id="mermaid-1577142258919Text"><g transform="translate(-39.4375,-9.5)"><foreignObject width="78.875" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">connection</div></foreignObject></g></g></g></g><g class="edgePaths"><g class="edgePath" style="opacity: 1;"><path class="path" d="M130.28125,65.25L155.28125,65.25L180.28125,65.25L205.78125,65.75" marker-end="url(#arrowhead106563)" style="fill:none"></path><defs><marker id="arrowhead106563" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath" style="opacity: 1;"><path class="path" d="M619.9238137755102,57.00387755102041L648.796875,55.25L673.796875,55.25L698.796875,57.97618844777645" marker-end="url(#arrowhead106564)" style="fill:none"></path><defs><marker id="arrowhead106564" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath" style="opacity: 1;"><path class="path" d="M698.796875,77.79732492758562L673.796875,82.5L648.796875,82.5L616.8659307753546,80.61188844929075" marker-end="url(#arrowhead106565)" style="fill:none"></path><defs><marker id="arrowhead106565" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g></g><g class="edgeLabels"><g class="edgeLabel" transform="" style="opacity: 1;"><g transform="translate(0,0)" class="label"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel" transform="" style="opacity: 1;"><g transform="translate(0,0)" class="label"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel" transform="" style="opacity: 1;"><g transform="translate(0,0)" class="label"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel"></span></div></foreignObject></g></g></g><g class="nodes"><g class="node" id="C" transform="translate(414.5390625,65.25)" style="opacity: 1;"><polygon points="9.75,0 408.765625,0 418.515625,-19.5 408.765625,-39 9.75,-39 0,-19.5" transform="translate(-209.2578125,19.5)" class="label-container"></polygon><g class="label" transform="translate(0,0)"><g transform="translate(-189.5078125,-9.5)"><foreignObject width="379.015625" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">verify sender (1) ---> pubsub <---> verify receiver (2)</div></foreignObject></g></g></g><g class="node" id="S" transform="translate(69.140625,65.25)" style="opacity: 1;"><rect rx="5" ry="5" x="-61.140625" y="-19.5" width="122.28125" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-51.140625,-9.5)"><foreignObject width="102.28125" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">sender (key 1)</div></foreignObject></g></g></g><g class="node" id="R" transform="translate(765.5,65.25)" style="opacity: 1;"><rect rx="5" ry="5" x="-66.703125" y="-19.5" width="133.40625" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-56.703125,-9.5)"><foreignObject width="113.40625" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">receiver (key 2)</div></foreignObject></g></g></g></g></g></g></svg>
|
Before Width: | Height: | Size: 16 KiB |
328
edge-messaging-implementation.md
Normal file
|
@ -0,0 +1,328 @@
|
|||
# Edge-messagng protocol implementation
|
||||
|
||||
This document defines specific elements to be used by client and server implementations of edge-messaging protocol. This protocol relies on the connection creation and messaging flows defined in generic [edge-messaging protocol][1].
|
||||
|
||||
This document defines:
|
||||
- [cryptographic algorithms](#cryptographic-algorithms) to sign/verify requests and to encrypt/decrypt messages.
|
||||
- required approaches to generate:
|
||||
- [connection IDs](#connection-id) for clients.
|
||||
- [connection URIs](#connection-uris) for servers.
|
||||
- [privacy requirements](#privacy-requirements) to the servers.
|
||||
- [REST API](#rest-api):
|
||||
- to create connections and to update connection attributes.
|
||||
- to send and to retrieve messages.
|
||||
- [WebSockets API](#websockets-api) to subscribe to connections:
|
||||
- to receive the new messages.
|
||||
- to update connection attributes.
|
||||
- any other requirements for edge-messaging servers
|
||||
|
||||
|
||||
## Cryptographic algorithms
|
||||
|
||||
Edge-messaging clients need to cryptographically sign requests:
|
||||
- with the recipient's key `RK` (server to verify):
|
||||
- to create connections.
|
||||
- to subscribe to connection.
|
||||
- to change connection attributes.
|
||||
- to delete the connection.
|
||||
- to retrieve messages from the connection
|
||||
- with the sender's key `SK`:
|
||||
- to send the initial request including public key `SK` (connection recipient to verify).
|
||||
- to send messages (server to verify).
|
||||
|
||||
To sign and verify requests, clients and servers MUST use RSA-PSS algorythm defined in [RFC3447][2].
|
||||
|
||||
To optinally sign and verify messages, clients SHOULD use RSA-PSS algorythm.
|
||||
|
||||
To encrypt and decrypt messages, clients SHOULD use RSA-OAEP algorythm defined in [RFC3447][2].
|
||||
|
||||
The reasons to support these algorithms:
|
||||
- they are supported by WebCrypto API.
|
||||
- they are newer versions than RSA-PKCS1-v1_5 encryption and signature schemes.
|
||||
- they are more widely supported than ECC algorithms
|
||||
|
||||
Future versions of the protocol may allow different algorithms.
|
||||
|
||||
|
||||
## Connection ID
|
||||
|
||||
Edge-messaging clients MUST generate random unique ID for each new unidirectional connection.
|
||||
|
||||
It is not required that this ID is globally unique across all clients and servers, and this ID is known only to the server on which connection is created and to the connection recipient.
|
||||
|
||||
Clients MUST use cryptographically strong pseudo-random number generator to generate 64(?)-bit connection IDs.
|
||||
|
||||
All requests (other than Create connection) require that `connectionID` property is passed to the server in the request body. This connection ID MUST match the client-generated `connectionID` earlier passed in the request to create the connection.
|
||||
|
||||
If connection IDs do not match, the server MUST reject the request with HTTP status code 404 (Not Found).
|
||||
|
||||
|
||||
## Connection URIs
|
||||
|
||||
Edge-messaging servers MUST generate 2 different URIs for each new connection - for recipient (that created the connection) and for sender. It is REQUIRED that:
|
||||
- these URIs are different.
|
||||
- they do not contain client-generated connection ID, any keys, or key hashes.
|
||||
- based on 64(?)-bit number generated with cryptographically strong pseudo-random number generator.
|
||||
|
||||
Coonection URIs can be:
|
||||
- server domain, path used to create connection and random string (e.g. `https://example.com/connection/aZ...9f), e.g. base-64 encoded random number.
|
||||
- any unique URI that server recognises.
|
||||
|
||||
|
||||
## Privacy requirements
|
||||
|
||||
Edge-messaging server implementations MUST NOT:
|
||||
- create any logs of the client requests in the production environment.
|
||||
- create any history of deleted connections or retrieved (and removed) messages.
|
||||
- create any history of connection updates and store old keys or URIs.
|
||||
- create any snapshots of the database they use to store connections and messages (instead edge-messaging clients must manage redundancy by using more than one edge-messaging server, e.g., as described in [graph-chat protocol][3]).
|
||||
- create/store any other information that may undermine privacy or [forward secrecy][4] of communication between clients using edge-messaging server.
|
||||
|
||||
|
||||
## REST API
|
||||
|
||||
### General API considerations
|
||||
|
||||
Edge-messaging server MUST provide REST API via HTTPS protocol. It MAY operate on the same domain as any other web application. It is RECOMMENDED that the endpoint to create connections and all connection URIs start from the same path, to avoid namespace conflicts with other applications.
|
||||
|
||||
In case of any requests sent to unknown URIs, server MUST reject the request with HTTP status code 404 (Not Found).
|
||||
|
||||
In case of any requests sent with missing required properties, incorrect property type/value or any additional unknown property (or sub-property), server MUST reject the request with HTTP status code 400 (Bad Request).
|
||||
|
||||
Below examples of API endpoints use:
|
||||
- server `https://example.com`
|
||||
- path `/connection`
|
||||
|
||||
|
||||
### Request headers
|
||||
|
||||
All server requests MUST use JSON object as request body and MUST use HTTP header `Content-Type: application/json`.
|
||||
|
||||
... client protocol version
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
### Request authorisation
|
||||
|
||||
All server requests MUST be signed with the relevant key and the digital signature MUST be passed in HTTP header `Authorization`.
|
||||
|
||||
In case of signature verification failure, server MUST reject the request with HTTP status code 401 (Unauthorised).
|
||||
|
||||
TODO Authorisation header format
|
||||
|
||||
|
||||
### Response headers
|
||||
|
||||
... server protocol version
|
||||
|
||||
... server timestamp
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
### REST API endpoints
|
||||
|
||||
Edge-messaging server MUST provide the API endpoints for the recipient and for the sender. The list of endpoints below has URI examples, the actual API URI schemes can differ between implementations, and even from deployment to deployment, based on server configuration.
|
||||
|
||||
URI scheme provides an additional layer of security to access the connection for both the sender and the recipient, and allows, if required, to implement and deploy private and commercial edge-messaging servers.
|
||||
|
||||
`messages` path segment in all endpoints to retrieve, delete and send messages is REQUIRED and MUST NOT be changed by any implementation or deployment.
|
||||
|
||||
Endpoints for the recipient:
|
||||
- [Create connection](#create-connection): POST `create URI` (e.g. `https://example.com/connection`)
|
||||
- [Update connection](#update-connection): PUT `<RU>` (e.g. `https://example.com/connection/aZ9f`)
|
||||
- [Delete connection](#delete-connection): DELETE `<RU>` (e.g. `https://example.com/connection/aZ9f`)
|
||||
- [Retrieve messages](#retrieve-messages): POST `<RU>/messages` (e.g. `https://example.com/connection/aZ9f/messages`)
|
||||
- [Delete messages](#delete-messages): DELETE `<RU>/messages` (e.g. `https://example.com/connection/aZ9f/messages`)
|
||||
|
||||
Endpoints for the sender:
|
||||
- [Update connection](#update-connection): PUT `<SU>` (e.g. `https://example.com/connection/bY1h`)
|
||||
- [Send messages](#send-messages): POST `<SU>/messages` (e.g. `https://example.com/connection/bY1h/messages`)
|
||||
|
||||
__Please note__: the server MUST NOT allow the sender to delete of modify the messages after they are sent.
|
||||
|
||||
|
||||
### REST API endpoints for the connection recipients
|
||||
|
||||
#### Create connection
|
||||
|
||||
URI: as defined by server configuration, can be different per server user.
|
||||
|
||||
Example: POST `https://example.com/connection`
|
||||
|
||||
Server MUST define a single endpoint to create connections. This endpoint can be:
|
||||
- server domain without any path, if the domain is not shared with other web application.
|
||||
- server domain and path used for all connection URIs.
|
||||
- server domain, path and secure token(s) (possibly user-specific), if the server owner wants to restrict access to creating connections (for private or commercial servers).
|
||||
- any other, potentially undiscoverable, URI that server recognises.
|
||||
|
||||
To create a connection, edge-messaging client MUST send POST request to this endpoint, signed with the key `RK`.
|
||||
|
||||
Request body should be sent as JSON object with the following properties:
|
||||
- `connectionID` (string): new client-generated connection ID.
|
||||
- `recipient` (string): public key `RK` to verify digital signature of the recipient.
|
||||
- `sender` (string, optional): public key `SK` to verify digital signature of the sender (it can be used in the alternative flow of establishing the connection when recipient and sender exchanged two secure out-of-band messages with each other, to reduce the number of connection steps - see [edge-messaging protocol][1]).
|
||||
- `disabled` (boolean, optional): if `true`, the connection will be created but it will not be possible for the sender to use it to send messages. It will still be possible to retrieve the available messages and to modify the connection.
|
||||
|
||||
Servers MUST require that connection ID is unique, and in the unlikely case of ID collision reject the connection creation request with HTTP status code 409 (Conflict).
|
||||
|
||||
If the connection creation succeeded, the server MUST respond with HTTP status code 201 (Created) and the response body MUST be a JSON object with the following properties:
|
||||
- `recipientURI` (string): recipient URI `RU` of the connection that MUST be used as the endpoint for requests to retrieve the messages, to update connection attributes and to delete the connection. Clients MUST NOT share this URI with the sender.
|
||||
- `senderURI` (string): sender URI `SU` of the connection that MUST be used as the endpoint for requests to send the messages.
|
||||
|
||||
|
||||
#### Update connection
|
||||
|
||||
URI: recipient connection URI `<RU>`
|
||||
|
||||
Example: PUT `https://example.com/connection/aZ9f`
|
||||
|
||||
To update the connection, edge-messaging client MUST send PUT request to the recipient connection URI `RU` (returned by the server when creating the connection), signed with the key `RK`.
|
||||
|
||||
Request body should be sent as JSON object with the following properties:
|
||||
- `connectionID` (string): existing connection ID (see [Connection ID](#connection-id)).
|
||||
- `recipient` (string, optional): public key `RK` to verify digital signature of the recipient, if the recipient wants to change the key (TBC - the request should be signed with both current and new key).
|
||||
- `sender` (string, optional/required): public key `SK` to verify digital signature of the sender. Unless this key was set when connection was created, this key is required on the first connection update request.
|
||||
- `newRecipientURI` (boolean, optional): if `true`, the server will generate a new URI `RU` for the recipient to use the connection.
|
||||
- `newSenderURI` (boolean, optional): if `true`, the server will generate a new URI `RU` for the sender to use the connection - it has to be passed to the sender so they can continue using the connection.
|
||||
- `disabled` (boolean, optional): if `true`, the connection will be "disabled" and it will not be possible for the sender to use it to send messages. It will still be possible to retrieve the available messages and for both sides to modify the connection. This parameter can be used to allow the back-pressure to the message sender (e.g. if the recipient is overloaded with message processing and cannot accept any new messages).
|
||||
|
||||
|
||||
Server MUST permanently update required connection keys and URIs without preserving any copy.
|
||||
|
||||
If the connection update succeeded, the server MUST respond with HTTP status code 200 (OK) with body (possibly empty) that may have the following properties:
|
||||
- `recipientURI` (string, optional): only returned if the new recipient URI was requested to be generated with `"newRecipientURI": true`.
|
||||
- `senderURI` (string, optional): only returned if the new sender URI was requested to be generated with `"newSenderURI": true`.
|
||||
|
||||
If any of the connection keys have changed, all the following requests signed with the old keys MUST be rejected with HTTP status code 401 (Unauthorised).
|
||||
|
||||
If any of the connection URIs have changed, all the following requests to the old URIs MUST be rejected with HTTP status code 404 (Not Found).
|
||||
|
||||
|
||||
#### Delete connection
|
||||
|
||||
URI: recipient connection URI `<RU>`
|
||||
|
||||
Example: DELETE `https://example.com/connection/aZ9f`
|
||||
|
||||
To delete the connection, edge-messaging client MUST send DELETE request to the recipient connection URI `RU` (returned by the server when creating the connection), signed with the key `RK`.
|
||||
|
||||
Request body should be sent as JSON object with the following properties:
|
||||
- `connectionID` (string): existing connection ID (see [Connection ID](#connection-id)).
|
||||
|
||||
If the connection deletion succeeded, the server MUST respond with HTTP status code 200 (OK) without body.
|
||||
|
||||
Server MUST permanently delete the connection and all unretrieved messages without preserving any copy of the connection or messages.
|
||||
|
||||
All further requests to the recipient and sender connection URIs MUST be rejected with HTTP status code 404 (Not Found).
|
||||
|
||||
|
||||
#### Retrieve messages
|
||||
|
||||
URI: `<RU>/messages`
|
||||
|
||||
Example: POST `https://example.com/connection/aZ9f/messages`
|
||||
|
||||
To retrieve messages from the connection, edge-messaging client MUST send POST request to the recipient connection URI `RU` (returned by the server when creating the connection) with the REQUIRED appended string `/messages` (it MUST NOT be changed by any implementation or deployment), signed with the key `RK`.
|
||||
|
||||
Request body should be sent as JSON object with the following properties:
|
||||
- `connectionID` (string): existing connection ID (see [Connection ID](#connection-id)).
|
||||
- `pageSize` (number, optional): if set, the server will return the number of messages, from the earliest available, up to the maximum of this parameter and `PAGE_SIZE` configured in the server. If not set the server will return up to `PAGE_SIZE` available messages.
|
||||
- `fromMessageID` (string, optional): if set, the server will retrieve the messages received starting from the message with server message ID (unique per server) passed in this parameter. This ID of the next available message is passed in the response to this request (if more messages are available).
|
||||
- `keepMessages` (boolean, optional): if `true`, the server will keep the retrieved messages available in the connection to be retrieved again (or deleted via a separate request). By default the retrieved messages will be removed from the server. Clients may need to process messages in some way, and until the processing succeded clients may choose to keep messages on the server to ensure they are not lost if processing fails for any reason.
|
||||
|
||||
Edge-messaging server MUST permanently remove the retrieved messages, unless specifically instructed by the clients to keep them.
|
||||
|
||||
__Please note__: server implementations MUST NOT track in any form how many times or whether the messages were retrieved.
|
||||
|
||||
If the unknown message ID is passed in `afterMessageID` parameter, the request should be rejected with HTTP status code 400 (Bad Request).
|
||||
|
||||
If the request is successful, the server MUST respond with HTTP status code (200) returning as response body the required number (but not more than `PAGE_SIZE` configured in the server) of the earliest sent messages with the following properties:
|
||||
- `messages` (array): retrieved messages. Each retrieved message is an object with the following properties:
|
||||
- `id`: server-generated unique ID allowing to identify messages until (they are deleted from the server) and to paginate responses.
|
||||
- `ts`: server timestamp of the time when the message was received from the sender.
|
||||
- `msg`: encrypted message body, that the recipient should be able to decrypt with the key `EK`.
|
||||
- `nextMessageID` (string, optional): if server has more messages available it MUST return this parameter that can be used by the next request in `fromMessageID` property.
|
||||
|
||||
|
||||
#### Delete messages
|
||||
|
||||
URI: `<RU>/messages`
|
||||
|
||||
Example: DELETE `https://example.com/connection/aZ9f/messages`
|
||||
|
||||
To delete messages from the connection, edge-messaging client MUST send DELETE request to the recipient connection URI `RU` (returned by the server when creating the connection) with the REQUIRED appended string `/messages` (it MUST NOT be changed by any implementation or deployment), signed with the key `RK`.
|
||||
|
||||
This request SHOULD be used by edge-messaging clients to delete the previously retrived messages when `"keepMessages": true` parameter was used or in case they no longer require to retrive the messages.
|
||||
|
||||
Request body should be sent as JSON object with the following properties:
|
||||
- `connectionID` (string): existing connection ID (see [Connection ID](#connection-id)).
|
||||
- `pageSize` (number, optional): if set, the server will delete up to the requested number of messages, otherwise all messages, in both cases from the message ID in `fromMessageID` parameter (or from the earliest available).
|
||||
- `fromMessageID` (string, optional): the server will delete the messages received, starting from the message with the server message ID passed in this parameter. If not specified, it defaults to the server message ID of the earliest received message.
|
||||
|
||||
Edge-messaging server MUST permanently remove the messages as requested.
|
||||
|
||||
If the request is successful, the server MUST respond with HTTP status code (200) with the body that has the count of deleted messages in `deleted` property.
|
||||
|
||||
If the unknown message ID is passed in `fromMessageID` parameter, the request should be rejected with HTTP status code 400 (Bad Request).
|
||||
|
||||
|
||||
### REST API endpoints for the connection sender
|
||||
|
||||
#### Update connection
|
||||
|
||||
URI: sender connection URI `<SU>`
|
||||
|
||||
Example: PUT `https://example.com/connection/bY1h`
|
||||
|
||||
To update the connection, edge-messaging client of the sender MUST send PUT request to the sender connection URI `SU` (returned by the server to the connection recipient when creating the connection), signed with the key `SK`.
|
||||
|
||||
Request body should be sent as JSON object with the following properties:
|
||||
- `connectionID` (string): existing connection ID (see [Connection ID](#connection-id)).
|
||||
- `sender` (string, optional): the new public key `SK` to verify digital signature of the sender. This parameter is only allowed if the sender key `SK` is already available on the connection, otherwise the server MUST reject the request with HTTP status code 401 (Unauthorised).
|
||||
- `newSenderURI` (boolean, optional): if `true`, the server will generate a new URI `RU` for the sender to use the connection.
|
||||
- `recipient`, `newRecipientURI`, `disabled`: these parameters are prohibited, and if any of them is present the server MUST reject the request with HTTP status code 401 (Unauthorised).
|
||||
|
||||
Server MUST permanently update required connection keys and URIs without preserving any copy.
|
||||
|
||||
If the connection update succeeded, the server MUST respond with HTTP status code 200 (OK) with body (possibly empty) that may have the following properties:
|
||||
- `recipientURI` (string, optional): only returned if the new recipient URI was requested to be generated with `"newRecipientURI": true`.
|
||||
- `senderURI` (string, optional): only returned if the new sender URI was requested to be generated with `"newSenderURI": true`.
|
||||
|
||||
If the connection key `SK` has changed, all the following requests signed with the old key MUST be rejected with HTTP status code 401 (Unauthorised).
|
||||
|
||||
If the connection URI `SU` has changed, all the following requests to the old URI MUST be rejected with HTTP status code 404 (Not Found).
|
||||
|
||||
|
||||
#### Send messages
|
||||
|
||||
URI: `<SU>/messages`
|
||||
|
||||
Example: POST `https://example.com/connection/bY1h/messages`
|
||||
|
||||
To send messages to the connection, edge-messaging client MUST send POST request to the recipient connection URI `RU` (returned by the server when creating the connection) with the REQUIRED appended string `/messages` (it MUST NOT be changed by any implementation or deployment), signed with the key `SK`.
|
||||
|
||||
Request body should be sent as JSON object with the following properties:
|
||||
- `connectionID` (string): existing connection ID (see [Connection ID](#connection-id)).
|
||||
- `messages` (array): retrieved messages. Each sent message is an object with the following properties:
|
||||
- `msg`: encrypted message body, that the recipient should be able to decrypt with the key `EK`. Any message meta-data (client timestamp, ID, etc.) MUST be inside the encrypted message and MUST NOT passed via additional properties.
|
||||
|
||||
If the request is successful, the server MUST respond with HTTP status code 200 (OK) without body.
|
||||
|
||||
|
||||
## WebSockets API
|
||||
|
||||
TODO
|
||||
|
||||
**Simplex connection operation:**
|
||||
|
||||

|
||||
|
||||
Sequence diagram does not show E2EE - connection itself knows nothing about encryption between sender and receiver.
|
||||
|
||||
|
||||
[1]: edge-messaging.md
|
||||
[2]: https://tools.ietf.org/html/rfc3447
|
||||
[3]: graph-chat.md
|
||||
[4]: https://en.wikipedia.org/wiki/Forward_secrecy
|
159
edge-messaging.md
Normal file
|
@ -0,0 +1,159 @@
|
|||
# Edge-messaging protocol
|
||||
|
||||
A generic client-server protocol for asynchronous distributed unidirectional messaging
|
||||
|
||||
## Problems of the existing messaging protocols
|
||||
|
||||
- Identity related problems:
|
||||
- visibility of user contacts to anybody observing messages
|
||||
- unsolicited messages (spam and abuse)
|
||||
- trademark issues (when usernames are used)
|
||||
- privacy issues (when phone numbers are used)
|
||||
|
||||
Participants' identities are known to the network. Depending on the identity type (e.g., phone number, DNS-based, username, uuid, public key, etc.) it creates different problems, but in all cases it exposes participants and their contacts graph to the network and also allows for unsolicited messages (spam and abuse).
|
||||
|
||||
- [MITM attack][1]. Any mechanism of the key exchange via the same network is prone to this type of attack when the public keys of the participants are substituted with the public keys of the attacker intercepting communication. While some solutions have been proposed that complicate MITM attack (social millionaire, OTR), if the attacker understands the protocol and has intercepted and can substitute all information exchanged between the participants, it is still possible to substitute encryption keys. It means that the existing [E2EE][2] implementations in messaging protocols and platforms can be compromised by the attacked who either compromised the server or communication channel.
|
||||
|
||||
|
||||
## Edge-messaging protocol abstract
|
||||
|
||||
The proposed "edge-messaging protocol" removes the need for participants' identities and provides [E2EE][2] without the possibility of [MITM attack][1] attack under one assumption: participants have an existing alternative communication channel that they trust and can use to pass one small binary message to initiate the connection (out-of-band message).
|
||||
|
||||
The out-of band message is sent via some trusted alternative channel by the connection recipient to the connection sender. This message is used to share the encryption (a.k.a. "public") key and connection URI requried to establish a unidirectional connection:
|
||||
- the sender of the connection (who received out-of-band message) will use it to send messages to the server using connection URI, signing the message by sender key.
|
||||
- the recepient of the connection (who created the connection and who sent out-of-band message) will use it to retrieve messages from the server, signing the requests by the recepient key.
|
||||
- participant identities are not shared with the server, as completely new keys and connection URI are used for each connection.
|
||||
|
||||
This unidirectional connection ("graph edge") is the main building block of the network that is used to build application level primitives (in graph-chat protocol) that are only known to system participants in their client applications (graph vertices) - user profiles, contacts, conversations, groups and broadcasts. At the same time, system servers are only aware of the low-level unidirectional connections (graph edges). In this way a high level of privacy and security of the conversations is provided. Application level chat primitives defined in graph-chat protocol are not in scope of this edge-messaging protocol.
|
||||
|
||||
This approach is based on the concepts of [unidirectional networks][4] that are used for applications with high level of information security.
|
||||
|
||||
Defining the approach to out-of-band message passing is out of scope of this edge-messaging protocol. For practical purposes, and from the graph-chat client application point of view, various solutions can be used, e.g. one of the versions or the analogues of [QR code][3] (or their sequence) that is read via the camera, either directly from the chat participant's device or via the video call. Although a video call still allows for a highly sophisticated MITM attack, it requires that in addition to compromising edge-messaging connection to intercept messages, the attacker also identifies and compromises the video connection in another channel and substitutes the video in real time - it seems extremely unlikely.
|
||||
|
||||
|
||||
## Simplex connection - the main unit of protocol design
|
||||
|
||||
The network consists of multiple "simplex connections" (i.e. unidirectional, non-duplex). Access to each connection is controlled with unique (not shared with other connections) assymetric key pairs, separate for sender and the receiver. The sender and the receiver have private keys, and the server has associated public keys to verify participants.
|
||||
|
||||
The messages sent into the connection are encrypted and decrypted using another key pair - the recepient has the private key and the sender has the associated public key.
|
||||
|
||||
**Unidirectional connection diagram:**
|
||||
|
||||

|
||||
|
||||
Connection is defined by ID (`ID`) unique to the server, sender URI `SU` and receiver URI `RU`. Sender key (`SK`) is used by the server to verify sender's requests (made via `SU`) to send messages. Recipient key (`RK`) is used by the server to verify recipient's requests (made via `RU`) to retrieve messages.
|
||||
|
||||
|
||||
## How Alice and Bob use edge-messaging protocol
|
||||
|
||||
Alice (recipient) wants to receive the messages from Bob (sender).
|
||||
|
||||
To do it Alice and Bob follow these steps:
|
||||
|
||||
1. Alice creates the unidirectional connection on the server:
|
||||
1. she decides which edge-messaging server to use (can be the same or different server that Alice uses for other connections).
|
||||
2. she generates a new random public/private key pair (encryption key - `EK`) that she did not use before for Bob to encrypt the messages.
|
||||
3. she generates another new random public/private key pair (recepient key - `RK`) that she did not use before for her to sign requests to retrieve the messages from the server.
|
||||
4. she generates a unique connection `ID` - generic edge-messaging protocol only requires that:
|
||||
- it is generated by the client.
|
||||
- it is unique on the server.
|
||||
5. she requests from the server to create a unidirectional connection. The request to create the connection is un-authenticated and anonymous. This connection definition contains previouisly generated connection `ID` and a uniqie "public" key `RK` that will be used to:
|
||||
- verify the requests to retrieve the messages as signed by the same person who created the connection.
|
||||
- update the connection, e.g. by setting the key required to send the messages (initially Alice creates the connection that accepts unsigned requests to send messages, so anybody could send the message via this connection if they knew the connection URI).
|
||||
6. The server responds with connection URIs:
|
||||
- recipient URI `RU` for Alice to retrieve messages from the connection.
|
||||
- sender URI `SU` for Bob to send messages to the connection.
|
||||
2. Alice sends an out-of-band message to Bob via the alternative channel that both Alice and Bob trust (see [Edge-messaging protocol abstract](#edge-messaging-protocol-abstract) above). The message includes:
|
||||
- the unique "public" key (`EK`) that Bob should use to encrypt messages.
|
||||
- the sender connection URI `SU` for Bob to use.
|
||||
3. Bob, having received the out-of-band message from Alice, accepts the connection:
|
||||
1. he generates a new random public/private key pair (sender key - `SK`) that he did not use before for him to sign requests to Alice's server to send the messages.
|
||||
2. he prepares the first message for Alice to confirm the connection. This message includes:
|
||||
- previously generated "public" key `SK` that will be used by Alice's server to verify Bob's requests to send messages.
|
||||
- optionally, any information that allows Alice to identify Bob (e.g., in [graph-chat protocol][7] it is Bob's chat profile, but it can be any other information).
|
||||
- optionally, any other additional information (e.g., Bob could pass the details of another connection including sender connection URI and a new "public" encryption key for Alice to send reply messages to Bob, also see [graph-chat protocol][7]).
|
||||
3. he encrypts the message by the "public" key `EK` (that Alice provided via the out-of-band message).
|
||||
4. he sends the encrypted message to the connection URI `SU` to confirm the connection (that Alice provided via the out-of-band message). This request to send the first message does not need to be signed.
|
||||
4. Alice retrieves Bob's message from the server via recipient connection URI `RU`:
|
||||
1. she decrypts retrieved message with "private" key `EK`.
|
||||
2. even though anybody could have sent the message to the connection `ID` before it is secured (e.g. if communication is compromised), Alice would ignore all messages until the decryption succeeds (i.e. the result contains the expected message structure). Optionally, she also may identify Bob using the information provided, but it is not required by this protocol.
|
||||
5. Alice secures the connection `ID` so only Bob can send messages to it:
|
||||
1. she sends the request to `RU` signed with "private" key `RK` to update the connection to only accept requests signed by "private" key `SK` provided by Bob.
|
||||
2. From this moment the server will accept only signed requests, and only Bob will be able to send messages to the `SU` corresponding to connection `ID`.
|
||||
6. The unidirectional connection `ID` is now established on the server.
|
||||
|
||||
**Creating unidirectional connection from Bob to Alice:**
|
||||
|
||||

|
||||
|
||||
|
||||
Bob now can securely send messages to Alice.
|
||||
|
||||
1. Bob sends the message:
|
||||
1. he encrypts the message to Alice with "public" key `EK` (provided by Alice, only known to Alice and Bob, used only for one unidirectional connection).
|
||||
2. he signs the request to the server (via `SU`) using the "private" key `SK` (that only he knows, used only for this connection).
|
||||
3. he sends the request to the server, that the server will verify using the "public" key SK (that Alice provided to the server).
|
||||
2. Alice retrieves the message(s):
|
||||
1. she signs request to the server with the "private" key `RK` (that only she has, used only for this connection).
|
||||
2. the server, having verified Alice's request with the "public" key `RK` that she provided, responds with Bob's message(s).
|
||||
3. she decrypts Bob's message(s) with the "private" key `EK` (that only she has).
|
||||
|
||||
**Sending messages from Bob to Alice via unidirectional connection:**
|
||||
|
||||

|
||||
|
||||
|
||||
A higher level protocol (e.g., [graph-chat][7]) defines the semantics that allow to use two unidirectional connections (or two sets of connections for redundancy) for the bi-directional messaging chat and for any other communication scenarios.
|
||||
|
||||
The edge-messaging protocol is intentionally unidirectional - it provides no answer to how Bob will know that the process succeeded, and whether Alice received any messages. There may be a situation when Alice wants to securely receive the messages from Bob, but she does not want Bob to have any proof that she received any messages - this low-level edge-messaging protocol can be used in this scenario, as all Bob knows as a fact is that he was able to send one unsigned message to the server that Alice provided, and now can only send messages signed with the key `SK` that he sent to the server - it does not prove that any message was received by Alice.
|
||||
|
||||
For practical purposes of bi-directional conversation, now that Bob can securely send encrypted messages to Alice, Bob can establish the second unidirectional connection that will allow Alice to send messages to Bob in the same way. If both Alice and Bob have their respective uniqie "public" keys (Alice's and Bob's `EK`s of two separate connections), the conversation can be both encrypted and signed.
|
||||
|
||||
The established connection can also be used to change the encryption keys providing [forward secrecy][5].
|
||||
|
||||
This protocol also can be used for off-the-record messaging, as Alice and Bob can have multiple connections established between them and only information they pass to each other allows proving their identity, so if they want to share anything off-the-record they can initiate a new connection without linking it to any other information they exchanged. As a result, this protocol provides better anonymity and better protection from [MITM][1] than [OTR][6] protocol.
|
||||
|
||||
How unidirectional connections (graph edges) are used by the participants (graph vertices) is defined by graph-chat protocol and is not in scope of this low level edge-messaging protocol.
|
||||
|
||||
|
||||
## Alternative flow to establish unidirectional connection
|
||||
|
||||
When Alice and Bob already have a secure duplex (bi-directional) communication channel that allows to conveniently send two out-of-band messages, a flow with smaller number of steps to establish the connection can be used.
|
||||
|
||||
TODO
|
||||
|
||||
**Alternative flow of creating unidirectional connection from Bob to Alice:**
|
||||
|
||||

|
||||
|
||||
|
||||
## Elements of the generic edge-messaging protocol
|
||||
|
||||
- defines only message-passing protocol:
|
||||
- transport agnostic - the protocol does not define how clients connect to the servers and does not require persistent connections. While a generic term "request" is used, it can be implemented in various ways - HTTP requests, messages over (web)sockets, etc. This is defined by edge-messaging server protocol.
|
||||
- not semantic - the protocol does not assign any meaning to connections and messages. While on the application level the connections and messages can have different meaning (e.g., for messages: text or image chat message, message acknowledgement, participant profile information, status updates, changing "public" key to encrypt messages, changing servers, etc.), on the edge-messaging protocol level all the messages are binary and their meaning can only be interpreted by client applications and not by the servers - this interpretation is in scope of graph-chat protocol and out of scope of this edge-messaging protocol.
|
||||
- client-server architecture:
|
||||
- multiple servers, that can be deployed by the system users, can be used to send and retrieve messages.
|
||||
- servers do not communicate with each other and do not even "know" about other servers.
|
||||
- clients only communicate with servers (excluding the initial out-of-band message), so the message passing is asynchronous.
|
||||
- for each connection, the message recipient defines the server through which the sender should send messages.
|
||||
- while multiple servers and multiple connections can be used to pass each chat message, it is in scope of graph-chat protocol, and out of scope of this edge-messaging protocol.
|
||||
- servers store messages only until they are retrieved by the recipients
|
||||
- servers are not supposed to store any message history or delivery log, but even if the server is compromised, it does not allow to decrypt the messages or to determine the list of connections established by any participant - this information is only stored on client devices.
|
||||
- the only element provided by edge-messaging servers is unidirectional connections (graph edges):
|
||||
- each connection is created and managed by the connection recipient.
|
||||
- assymetric encryption is used to sign and verify the requests to send and receive the messages.
|
||||
- one unique "public" key is used for the servers to authenticate requests to send the messages into the connection, and another unique "public" key - to retrieve the messages from the connection. "Unique" here means that each "public" key is used only for one connection and is not used for any other context - effectively this key is not public and does not represent any participant identity.
|
||||
- both "public" keys are provided to the server by the connection recepient when the connection is established.
|
||||
- the "public" keys known to the server and used to authenticate requests from the participants are unrelated to the keys used to encrypt and decrypt the messages - the latter keys are also unique per each connection but they are only known to participants, not to the servers.
|
||||
- messaging graph can be asymmetric: Bob's ability to send messages to Alice does not automatically lead to the Alice's ability to send messages to Bob.
|
||||
- connections are identified by the "connection URI" - server URI and connection ID (`ID`).
|
||||
|
||||
|
||||
[1]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
|
||||
[2]: https://en.wikipedia.org/wiki/End-to-end_encryption
|
||||
[3]: https://en.wikipedia.org/wiki/QR_code
|
||||
[4]: https://en.wikipedia.org/wiki/Unidirectional_network
|
||||
[5]: https://en.wikipedia.org/wiki/Forward_secrecy
|
||||
[6]: https://en.wikipedia.org/wiki/Off-the-Record_Messaging
|
||||
[7]: graph-chat.md
|
220
graph-chat.md
Normal file
|
@ -0,0 +1,220 @@
|
|||
# Graph-chat protocol
|
||||
|
||||
A generic chat protocol for client applications that communicate via edge-messaging protocol
|
||||
|
||||
|
||||
## Problems of the existing chat platforms and protocols
|
||||
|
||||
- Dependency on a single company/server to access and use chat. That creates implications for chat privacy, user profile resilience and data ownership.
|
||||
- Visiblity of user profile information to chat system and other chat users. Chat users have limited control as to who can see and find their profile.
|
||||
- Visibility of user contacts graph to chat server.
|
||||
- [E2EE][1] can be compromised by [MITM][2] attack (see [edge-messaging protocol][3]).
|
||||
- Identity related problems (also see [edge-messaging protocol][3]).
|
||||
|
||||
|
||||
## Graph-chat protocol abstract
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
## Duplex connection
|
||||
|
||||
Majority of chat scenarios requires duplex (bi-directional) connections between participants. Graph-chat protocol uses multiple unidirectional connections (graph edges) created on multiple edge-messaging servers to implement duplex connections.
|
||||
|
||||
Each duplex connection consists of one or multiple, for redundancy, pairs of unidirectional connections to connect chat participants or devices of the same participant - it is used for "contacts", "devices", "group participants", etc. For practical purposes of redundancy, chat clients can use 2-4 pairs of unidirectional connections.
|
||||
|
||||
The process described below establishes a duplex connection between Alice and Bob that has `n` unidirectional connections `CAi` (where `1 <= i <= n`) from Bob to Alice (created by Alice on her servers) and `n` unidirectional connections `CBi` (where `1 <= i <= n`) from Alice to Bob (created by Bob on his servers).
|
||||
|
||||
The following symbols are used below:
|
||||
- unidirectional edge-messaging connections:
|
||||
- `CAi` - Alice's edge-messaging connection number `i` (out of `n`) allowing Bob to send messages to Alice.
|
||||
- `CBi` - Bob's edge-messaging connection number `i` (out of `n`) allowing Alice to send messages to Bob.
|
||||
- keys created for Alice's connections:
|
||||
- `EKAi` - Alice's assymetric key pair used:
|
||||
- by Bob to encrypt and by Alice to decrypt messages from Bob sent via `CAi`.
|
||||
- by Alice to sign and Bob to verify messages from Alice sent via `CBi`.
|
||||
- `IDAi` - client-generated ID of connection `CAi`
|
||||
- `RUAi` - server-generated recipient connection URI of `CAi` (to be used by Alice to retrieve messages).
|
||||
- `SUAi` - server-generated sender connection URI of `CAi` (to be used by Bob to send messages).
|
||||
- `RKAi` - Alice's recipient key of `CAi`.
|
||||
- `SKAi` - Bob's sender key of `CAi`.
|
||||
- keys created for Bob's connections
|
||||
- `EKBi` - Bob's assymetric key pair used for:
|
||||
- Alice to encrypt and Bob to decrypt messages from Alice sent via `CBi`.
|
||||
- Bob to sign and Alice to verify messages from Bob sent via `CAi`.
|
||||
- `IDBi` - client-generated ID of connection `CBi`
|
||||
- `RUBi` - server-generated recipient connection URI of `CBi` (to be used by Bob to retrive messages).
|
||||
- `SUBi` - server-generated sender connection URI of `CBi` (to be used by Alice to send messages).
|
||||
- `RKBi` - Bob's recipient key of `CBi`.
|
||||
- `SKBi` - Alice's sender key of `CBi`.
|
||||
|
||||
|
||||
### Creating duplex connection
|
||||
|
||||
To create a duplex connection initiated by Alice, Alice's and Bob's apps follow these steps:
|
||||
|
||||
1. Alice's app initiates duplex connection:
|
||||
1. it creates `n` unidirectional connections ("edges") `CAi` (step 1 in [edge-messaging][3]) that are defined by:
|
||||
- client-generated:
|
||||
- Alice's asymmetric key pairs `EKAi` to encrypt messages.
|
||||
- connection IDs `IDAi`.
|
||||
- recipient keys `RKAi`.
|
||||
- server-generated:
|
||||
- recipient URIs `RUAi`.
|
||||
- sender URIs `SUAi`.
|
||||
2. optionally, Alice's app subsribes to receive messages from these new connections `CAi` ([edge-messaging protocol implementation][4] defines protocol to be used for subscriptions).
|
||||
2. Alice's app sends a secure message to Bob's app (step 2 in [edge-messaging][3]):
|
||||
1. it prepares the message with the information needed to establish all connections `CAi`, including all encryption keys `EKAi` and sender connection URIs (`SUAi`).
|
||||
2. depending on the communication scenario, Alice's app sends this message to Bob's app in one of available secure ways (either out-of-band or via available secure duplex connection(s) - see [Duplex connection security level](#TODO)), depending on communication scenario:
|
||||
- if Bob is a new contact, the information is presented as a visual code (e.g. QR code(s)) to Bob's app - as out-of-band message needed to establish connections `CAi` (see [edge-messaging protocol][3] and [Adding direct contact](#adding-direct-contact)).
|
||||
- if Bob is added to group chat with Alice by some contact of Alice or Bob, this information can be passed through all possible chains of contacts between Alice and Bob in the group (to minimise the risk of MITM attack) (see [Group chat](#TODO)).
|
||||
- if Alice adds Bob as a contact via Bob's trusted contact John, who also has Alice as a trusted contact, this information will be passed via John (who has secure duplex connections with both Alice and Bob), in the same way as with Group chat (see [Trusted contacts](#TODO)).
|
||||
- if Alice already has Bob as a contact, and she wants to create a separate off-the-record chat with him, this information will be passed via their existing connection (see [Off-the-record chat](#TODO)).
|
||||
- etc. In all cases, the protocol defines the most secure possible way to pass the out-of-band (from the point of view of the new connections) message required by [edge-messaging protocol][3] to create unidirectional connections.
|
||||
3. Bob's app accepts duplex connection with Alice:
|
||||
1. it receives the message from Alice's app, in a way defined by a specific chat scenario.
|
||||
2. it interprets the received information as edge-messaging protocol out-of-band messages to accept all unidirectional connections `CAi`.
|
||||
3. it creates `n` new unidirectional connections `CBi` on Bob's servers defined by:
|
||||
- client-generated:
|
||||
- Bob's asymmetric key pairs `EKBi` to encrypt messages.
|
||||
- connection IDs `IDBi`.
|
||||
- recipient keys `RKBi`.
|
||||
- server-generated:
|
||||
- recipient URIs `RUBi`.
|
||||
- sender URIs `SUBi`.
|
||||
4. optionally, Bob's app subsribes to receive messages from these new connection `CBi` (see [edge-messaging protocol implementation][4]).
|
||||
5. it proceeds with accepting Alice's app connections `CAi` (step 3 in [edge-messaging protocol][3]).
|
||||
6. in response to each connection `CAi`, as optional information, Bob's app includes:
|
||||
- Bob's user profile (that is only stored in graph-chat client and not visible to any server)
|
||||
- information to establish connection `CBi`:
|
||||
- encryption key `EKBi`.
|
||||
- sender connection URIs (`SUBi`).
|
||||
7. his app now sends the unsigned requests to Alice's connections `CAi` (step 3.4 in [edge-messaging protocol][3]), to both confirm the connections `CAi` and to propose the new connections `CBi`. As each message is encrypted by the key `EKAi` of the connection `CAi` that only Alice can decrypt, it is safe to send it - from edge-messaging server point of view it is an out-of-band message.
|
||||
4. Alice's app adds Bob's duplex connection:
|
||||
1. it receives the messages from Bob via connections `CAi` (step 4 in [edge-messaging protocol][3]).
|
||||
2. depending on chat scenario, Bob is identified and confirmed:
|
||||
- for new contact, Alice may visually identify Bob's user profile and accepts Bob as a contact.
|
||||
- for group participant, Alice's app will match known Bob's user profile ID with received user profile ID (that is only visible to the clients apps that have this profile).
|
||||
3. it secures the connections `CAi` with keys `SKAi` received from Bob - the connections are now established (step 5 in [edge-messaging protocol][3]).
|
||||
4. it accepts the connections `CBi`, including in the response to Bob's server Alice's user profile and (as the additional information) the confirmation that the connections `CAi` are secured and can be used (step 3 in [edge-messaging protocol][3]).
|
||||
5. it sends the unsigned messages via connections `CBi`.
|
||||
6. it adds Bob's duplex connection to the list of available duplex connections as "pending" (Alice cannot yet send messages to Bob, but Bob already can send messages to Alice). Possibly, the status of duplex connection can indicate that Bob already can send messages to Alice.
|
||||
5. Bob's app adds duplex connection with Alice:
|
||||
1. it receives the initial messages via connections `CBi`.
|
||||
2. it secures the connections `CBi` - they are now established as well (step 5 in [edge-messaging protocol][3]).
|
||||
3. it adds duplex connection with Alice in the list of available duplex connections as "pending" (to indicate that Alice cannot yet send messages). Possibly, the status of duplex connection can indicate that Bob already can send messages to Alice.
|
||||
4. it sends a special message (message type "welcome") to Alice's app via duplex connection (i.e., via all connections `CAi`) to confirm that adding Alice's contact is completed (see [Sending messages via duplex connection](#TODO)).
|
||||
6. Alice's app finalises adding duplex connection with Bob:
|
||||
1. it receives "welcome" message from Bob's app via duplex connection.
|
||||
2. it changes Bob's duplex connection status to "established".
|
||||
3. it sends a special "welcome" message to Bob's app via duplex connection.
|
||||
7. Bob's app finalises adding duplex connection with Alice:
|
||||
1. it receives "welcome" message from Alice's app via duplex connection.
|
||||
2. it changes Alice's duplex connection status "established".
|
||||
|
||||
**Creating duplex connection between Alice and Bob:**
|
||||
|
||||

|
||||
|
||||
|
||||
### Sending message via duplex connection
|
||||
|
||||
When Alice sends the message to Bob via the duplex connection, they follow these steps:
|
||||
|
||||
1. Alice sends the message to Bob in the app:
|
||||
1. her app prepares the message, including:
|
||||
- client-generated message ID (unique per duplex connection).
|
||||
- client timestamp.
|
||||
- message body.
|
||||
2. her app prepares a separate version of the message for each connection `CBi`, by signing each version of the message with the corresponding key `EKAi` and encrypting it with `EKBi`.
|
||||
3. her app sends all copies of the message to corresponding connections `CBi`.
|
||||
2. Bob's app receives the message from Alice:
|
||||
1. it retrieves all versions of the message via all connections `CBi` (they can arrive at different time and possibly out of order with other messages).
|
||||
2. it decrypts each version with `EKBi` and verifies the signature with `EKAi`.
|
||||
3. the first successfully decrypted message version is added to the Bob's chat with Alice and notification can be shown to Bob; if message verification failed it is marked as "unverified" in the chat.
|
||||
4. all subsequently decrypted and verified copies are discarded.
|
||||
- if some of the decryptions or verifications fail, the corresponding connection is marked as "possibly compromised" and has to be replaced by the app with another connection.
|
||||
- if decryption or verification fails for all connections `CBi`, Alice's contact is marked as "compromised".
|
||||
3. Bob's app sends "message received" to Alice's app:
|
||||
1. it prepares a special "message received" message, including:
|
||||
- client-generated message ID (unique per duplex connection).
|
||||
- correlation ID of the message received from Alice.
|
||||
- client timestamp of the message reception.
|
||||
2. it prepares a separate version of the message for each connection `CAi`, by signing each version of the message with the corresponding key `EKBi` and encrypting it with `EKAi`.
|
||||
3. it sends all copies of the message to corresponding connections `CAi`.
|
||||
4. Alice's app receives "message received" from Bob's app:
|
||||
1. it retrieves all versions of the "message received" message via all connections `CAi` (they can arrive at different time and possibly out of order with other messages).
|
||||
2. it decrypts each version with `EKAi` and verifies the signature with `EKBi`.
|
||||
3. once the first version is successfully decrypted and verified, the previosly sent message in the Alice's chat with Bob is marked as delivered.
|
||||
4. all subsequently decrypted and verified copies are discarded.
|
||||
- if some of the decryptions or verifications fail, the corresponding connection is marked as "possibly compromised" and has to be replaced by the app with another connection.
|
||||
- if decryption or verification fails for all connections `CAi`, Bob's contact is marked as "compromised".
|
||||
|
||||
**Sending message from Alice to Bob via duplex connection:**
|
||||
|
||||

|
||||
|
||||
|
||||
## Adding direct contact
|
||||
|
||||
"Direct contact" is a term used for a duplex connection with another chat user established directly, not via another user. Establishing contact requires sending an out-of-band message - "visual code" (e.g. QR code(s)) is used for this purpose.
|
||||
|
||||
For example, Alice and Bob want to have a conversation in chat app, exchanging messages with each other. They both have graph-chat client and access to several edge-messaging servers (see [edge-messaging protocol][3]) that they can use to receive messages, and their graph-chat clients are configured to use these servers.
|
||||
|
||||
To chat in the app Alice needs to add Bob as "direct contact" to her contacts in the app.
|
||||
|
||||
1. Alice initiates adding "direct contact" in her graph-chat client app.
|
||||
2. Alice shares "visual code" (e.g. QR code(s)) with Bob:
|
||||
1. her app prepares secure message to establish duplex connection with Bob's app (step 1 in [Creating duplex connection](#creating-duplex-connection)).
|
||||
2. her app prepares and displays a visual code with this message (step 2 in [Creating duplex connection](#creating-duplex-connection)).
|
||||
3. Alice now can share this visual code with Bob, either in person or via a video call (see [edge-messaging protocol][3]).
|
||||
3. Bob reads "visual code" that Alice prepared via his app:
|
||||
1. he initiates adding "direct contact" via visual code in his graph-chat client app.
|
||||
2. his app interprets this visual code as secure message required to accept duplex connection with Alice and proceeds with creating duplex connection (step 3 in [Creating duplex connection](#creating-duplex-connection)).
|
||||
4. Alice's app adds Bob as "direct contact":
|
||||
1. it proceeds with creating duplex connection.
|
||||
2. once "pending" duplex connection with Bob's app is created in Alice's app, Bob is added as "direct contact" with the status "pending" in Alice's app (step 4 in [Creating duplex connection](#creating-duplex-connection)).
|
||||
5. Bob's app adds Alice as "direct contact":
|
||||
1. it proceeds with creating duplex connection.
|
||||
2. once "pending" duplex connection with Alice's app is created in Bob's app, Alice is added as "direct contact" with the status "pending" in Bob's app (step 5 in [Creating duplex connection](#creating-duplex-connection)).
|
||||
6. Alice's app finalises adding Bob as "direct contact":
|
||||
1. it finalises adding duplex connection with Bob's app (step 6 in [Creating duplex connection](#creating-duplex-connection)).
|
||||
2. it changes Bob's "direct contact" status to "established".
|
||||
7. Bob's app finalises adding Alice as "direct contact":
|
||||
1. it finalises adding duplex connection with Alice's app (step 7 in [Creating duplex connection](#creating-duplex-connection)).
|
||||
2. it changes Alice's "direct contact" status to "established".
|
||||
|
||||
|
||||
## Connection and message types used in graph-chat protocol
|
||||
|
||||
##### Duplex connections
|
||||
|
||||
TODO
|
||||
|
||||
- "contact" - can be of type "person", "bot", "device", "organisation".
|
||||
- "group-participant" - connection to another group participant that was established via the chain of other contacts in the group.
|
||||
- "broadcast" - duplex connection from broadcast subscriber to publisher; client apps should only allow to receive messages and send back control messages, but not the content messages. Profile of the subscriber is not shared with the publisher.
|
||||
- "broadcast-subscriber" - duplex connection from broadcast publisher to subscriber.
|
||||
|
||||
|
||||
##### Messages
|
||||
|
||||
Control messages:
|
||||
|
||||
- "welcome" - sent and recieved by both participants when duplex connection is being established.
|
||||
- "receipt" - acknowledging message receipt.
|
||||
- "contact: update" - changing "contact" profile .
|
||||
- "profile" - changing "contact
|
||||
- "gm: add" - sent to add unidirectional edge-messaging connection to graph-chat duplex connection.
|
||||
- "gm: remove" - sent to remove unidirectional edge-messaging connection from graph-chat duplex connection.
|
||||
|
||||
Content messages:
|
||||
|
||||
- "text"
|
||||
- "image"
|
||||
|
||||
|
||||
[1]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
|
||||
[2]: https://en.wikipedia.org/wiki/End-to-end_encryption
|
||||
[3]: edge-messaging.md
|
||||
[4]: edge-messaging-implementation.md
|
192
readme.md
|
@ -1,23 +1,25 @@
|
|||
# Federated chat system with E2EE and low risk of MITM attack
|
||||
# Federated chat system with [E2EE][1] and low risk of [MITM][2] attack
|
||||
|
||||
## Problems
|
||||
|
||||
The problems of existing chat solutions this system intends to solve:
|
||||
Existing chat platforms and protocols have some or all of the following problems:
|
||||
- privacy of the conversation, partially caused by [E2EE][1] implementations.
|
||||
- privacy of the user profile and connections.
|
||||
- unsolicited messages (spam and abuse).
|
||||
- lack of data ownership and protection.
|
||||
- complexity of usage for all non-centralised protocols to all non-technical users
|
||||
|
||||
- Dependency on a single company/server to access and use chat. That creates implications for chat privacy (see E2EE), user profile resilience and data ownership.
|
||||
- Existing [E2EE](https://en.wikipedia.org/wiki/End-to-end_encryption) implementations in chat platforms are easily compromised via the platform itself with [MITM attack](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) - as the key exchange happens via the same channel, the key can be substituted.
|
||||
- Visiblity of user profile information to chat system and other chat users.
|
||||
- Spam and exposing the list of connections to the chat system: when using any user identity to send messages (whether usernames, phone numbers, unique IDs or DNS-based addresses).
|
||||
- Other problems caused by specific types of user identities:
|
||||
- DNS-based (e.g. with XMPP protocol). While DNS is useful for shared information access, connecting chat users to particular domains undermines users privacy and gives control over chat users communications to domain owners.
|
||||
- Phone numbers. Most phone numbers either expose or allow to access private information about the user, including personal identity.
|
||||
- User names. They must be unique, but they can violate existing trademarks and be reclaimed by trademark owners, lead to name squatting and can leak private information about the users.
|
||||
Some of these problems are covered in more details in the proposed protocols on which this chat system will be based on:
|
||||
- [edge-messaging][6] - low level client-server protocol for asynchronous distributed unidirectional messaging
|
||||
- [graph-chat][8] - high level chat protocol for client applications that communicate via edge-messaging protocol
|
||||
|
||||
Even though EU-wide GDPR legislation to ensure users' privacy and data protection was adopted, the centralisation of the communication in a small number of platforms makes resolving these problems quite difficult.
|
||||
|
||||
|
||||
## Requirements
|
||||
## Privacy requirements
|
||||
|
||||
- User profile is only visible to the proifle connections, but not to the chat network
|
||||
- User profile is not stored on the server.
|
||||
- User profile is not stored on the servers.
|
||||
- Profile connections are not stored on the server.
|
||||
- It should not be possible to construct the list of user connections by analysing server database or any logs.
|
||||
- It should not be possible, other than by compromising the client, to send messages from another user profile.
|
||||
|
@ -25,140 +27,106 @@ The problems of existing chat solutions this system intends to solve:
|
|||
- All participants of the conversation should be able to:
|
||||
- prove that the received messages were actually sent by another party.
|
||||
- prove that the sent messages were received by another party.
|
||||
- prove sent/received time/delivery order - with the solution below only timestamp of another party can be proven, not the actual time.
|
||||
|
||||
|
||||
## Other ideas
|
||||
## Chat scenarios
|
||||
|
||||
- [OTR messaging](https://en.wikipedia.org/wiki/Off-the-Record_Messaging) - below defines verifiable messaging, there is also value in supporting off-the-record messaging. Not in prototype
|
||||
- Group support - 1) separately encrypt for each recepient or 2) encrypt once per group using distributed symmetric key or 3) ... ? Probably encrypt separately, at least initially.
|
||||
- Multiple user devices - 1) known to servers (servers broadcast) or 2) known to senders (senders send to multiple devices) or 3) known to device owners (once one device receives it forwards to other devices; messages expire)? Probably known to device owners. Not in prototype.
|
||||
- Introductions - with the current design, user must initiate the connection before anybody can send messages to them. Can this be delegated to another user? One-off introduction? Trusted users? Is adding to group amounts to introduction to all users in group?
|
||||
Any advanced chat system needs to support several main chat scenarios:
|
||||
- direct messaging.
|
||||
- group chat.
|
||||
- broadcasts.
|
||||
|
||||
In addition to that, there are other important chat scenarios:
|
||||
- [OTR messaging][3]
|
||||
- multiple user devices sharing contacts and conversation histories.
|
||||
- introductions
|
||||
|
||||
While it is not required to be supported in the v1 of the protocol, it is important to have clarity on how all these scenarios can be supported in the future.
|
||||
|
||||
|
||||
## Solution
|
||||
## Chat system features
|
||||
|
||||
- No phone numbers, user names and no DNS are used to identify to users (chat servers may choose to be accessible via DNS, but it is not required)
|
||||
- Each user is connected to multiple servers to ensure message delivery even if some of the servers are compromised
|
||||
- Using standard assymetric encryption protocol, so system users can create independent server and client implementations
|
||||
- Open-source server implementations that can be easily deployed by any user with minimal expertise (e.g. on Heroku via web UI).
|
||||
- No user identity known to system servers - no phone numbers, user names and no DNS are used to identify the users to the system.
|
||||
- Each user can be connected to multiple servers to ensure message delivery even if some of the servers are compromised.
|
||||
- Uses standard assymetric cryptographic protocols, so system users can create independent server and client implementations complying with the protocols.
|
||||
- Open-source server implementations that can be easily deployed by any user with minimal technical expertise (e.g. on Heroku via web UI).
|
||||
- Open-source mobile client implementations (including web client) so that system users can independently assess system security model.
|
||||
- Only client applications store user profiles, connections to other user profiles and messages; servers do NOT have access to any of this information (and unless compromised, do NOT store even encrypted messages).
|
||||
- Multiple client applications and devices can be used by each user profile to communicate and to share connections and message history.
|
||||
- Key exchange and establishing connections between user profiles is done by sharing QR code via any independent communication channel (or directly via screen and camera), system servers are NOT used for key exchange - to reduce risk of key substitution in MITM attack. QR code contains the connection-specific public key and user's current servers.
|
||||
- Only client applications store user profiles, contacts of other user profiles, messages; servers do NOT have access to any of this information (and unless compromised, do NOT store encrypted messages or any logs).
|
||||
- Multiple client applications and devices can be used by each user profile to communicate and to share connections and message history - the devices are not known to the servers.
|
||||
- Initial key exchange and establishing connections between user profiles is done by sharing QR code via any independent communication channel (or directly via screen and camera), system servers are NOT used for key exchange - to reduce risk of key substitution in [MITM][2] attack. QR code contains the connection-specific public key and other information needed to establish connection.
|
||||
- Connections between users can be established via shared trusted connections to simplify key exchange.
|
||||
- Servers do NOT communicate with each other, they only communicate with client applications.
|
||||
- Unique public key is used for each user profile connection in order to:
|
||||
- reduce the risk of attacker posing as user's connection
|
||||
- avoid exposing all user connections to the servers
|
||||
- Unique public key is used to identify each connection participant to each server.
|
||||
- Public keys used between connections are regularly rotated to prevent decryption of the full message history ([forward secrecy](https://en.wikipedia.org/wiki/Forward_secrecy)) in case when some servers or middle-men preserve message history and the current key is compromised.
|
||||
- Users can repeat key exchange using QR code and alternative channel at any point to increase communication security.
|
||||
- Public keys used between connections are regularly rotated to prevent decryption of the full message history ([forward secrecy][4]) in case when some servers or middle-men preserve message history and the current key is compromised.
|
||||
- Users can repeat key exchange using QR code and alternative channel at any point to increase communication security and trust.
|
||||
- No single server in the system has visibility of all connections or messages of any user, as user profiles are identified by multiple rotating public keys, using separate key for each profile connection.
|
||||
- Servers only store hashes of public keys, so that nobody can encrypt messages from a pending user profile connection while the connection is established, other than the user who has this public key received via QR code (in addition to that, messages are signed once key exchange is done both ways).
|
||||
- User profile (meta-data of the user including non-unique name / handle and optional additional data, e.g. avatar and status) is stored in the client apps and is shared only with accepted user profile connections.
|
||||
|
||||
|
||||
## System components
|
||||
|
||||
- chat servers
|
||||
- chat client applications
|
||||
- edge-messaging servers
|
||||
- graph-chat client applications (using edge-messaging protocol to communicate with the servers)
|
||||
|
||||
|
||||
## System functionality
|
||||
### Chat servers
|
||||
|
||||
### Chat server
|
||||
|
||||
Chat servers can be either available to all users or only to users who have a valid server key.
|
||||
|
||||
Chat servers have to provide the following:
|
||||
|
||||
- receive messages to connected user profiles
|
||||
- send messages to client apps of connected recepients
|
||||
- identify recepients by hashes of public keys, unique for each recepient-sender connection (one-directional)
|
||||
- verify recepients via signatures using recepients' server public keys for each connection.
|
||||
- verify senders signatures using senders' server public keys for each connection (provided by recepient).
|
||||
- servers never exchange messages with other servers
|
||||
Edge-messaging servers can be either available to all users or only to users who have a valid URI to create connections (see [edge-messaging protocol][6]).
|
||||
|
||||
|
||||
### Chat client application
|
||||
|
||||
Chat applications are installed by users on their devices
|
||||
Graph-chat applications are installed by users on their devices.
|
||||
|
||||
Client apps should provide the following:
|
||||
Client apps should provide the following features:
|
||||
|
||||
- create and manage user profiles - user can have multiple profiles.
|
||||
- create and manage user profiles
|
||||
- support multiple user profiles.
|
||||
- share access to all or selected user profiles with other devices (optionally including existing connections and message histories).
|
||||
- for each user profile:
|
||||
- generate and show QR code with connection-specific public key and profile servers that can be shown on the screen and/or sent via alternative channel.
|
||||
- track whether QR code was shared (assuming lower connection security if it wasn't read directly from the screen).
|
||||
- generate and show QR code with connection-specific public key and other information that can be shown on the screen and/or sent via alternative channel.
|
||||
- read QR code (via the camera) to establish connection with another user.
|
||||
- receive and accept connection requests (it would contain, in encrypted form (using recepient public key that was previously shared), requesting user public key to use to encrypt messages and user's servers).
|
||||
- exchange user profiles data once connection is accepted.
|
||||
- send messages to connected user profiles (message would contain hash of connection-specific public key of the recepient and encrypted message) via the servers that the recepient is connected to.
|
||||
- receive messages from connected user profile (message will be decrypted in the client using recepient's private key associated with the sender) via the servers that the recepient is connected to.
|
||||
- optionally define the servers they trust and will connect to, including the servers that require server keys to access.
|
||||
- receive and accept connection requests.
|
||||
- exchange user profiles once connection is accepted.
|
||||
- send messages to connected user profiles.
|
||||
- receive messages from connected user profile.
|
||||
- define the servers to use.
|
||||
- store history of all conversations encrypted using user client app key with passphrase (or some other device specific encryption method).
|
||||
|
||||
|
||||
## System design
|
||||
|
||||
Prepared with [mermaid-js](https://mermaid-js.github.io/mermaid-live-editor)
|
||||
The chat system design is based on 2 protocols, each with the generic part, describing protocol flow and logic, and implementation part, describing protocol transports, data structures and algorithms.
|
||||
|
||||
1. [edge-messaging protocol][6] - a low level generic messaging protocol that defines establishing and using a unidirectional connection ("graph edge") between chat participants on a single server. While this protocol is designed to support graph-chat client protocol (below), it can be used for other messaging scenarios, not limited to chats.
|
||||
2. [edge-messaging protocol implementation][7] - requirements to clients and servers implementing edge-messaging protocol, including:
|
||||
- cryptographic algorithms to sign/verify requests and to encrypt/decrypt messages.
|
||||
- privacy requirements to the servers.
|
||||
- REST API for connections and messages.
|
||||
- WebSocket API to subscribe to connections and receive new messages.
|
||||
- other requirements for edge-messaging servers.
|
||||
3. [graph-chat protocol][8] - a high level generic chat protocol for client applications (graph vertices) that communicate via connections (graph edges) created using edge-messaging protocol. This protocol defines connection and message types and semantics for:
|
||||
- various chat elements (user profiles, direct chats, chat groups, broadcasts, etc.).
|
||||
- other communication scenarios - e.g. introduction, off-the-record chat, etc.
|
||||
- using multiple servers to ensure message delivery.
|
||||
- sharing user profiles, contacts and chats across multiple client devices.
|
||||
- changing cryptographic keys and servers used to send and receive messages using edge-messaging server protocol.
|
||||
- sending and receiving out-of-band messages between client applications using "visual code".
|
||||
4. graph-chat client application protocol (TODO) - a high level specific chat protocol for client applications. This protocol specifies:
|
||||
- data structures for sending and receiving messages of all types.
|
||||
- process to send and receive out-of-band messages between client applications.
|
||||
- other requirements for graph-chat client applications.
|
||||
- defines a specific "visual code" format to send an out-of-band message.
|
||||
|
||||
|
||||
## Client-server architecture
|
||||
|
||||
To allow sending the message when receiver client is not available, and to allow receiving the messages when the sender client is not available, the system relies on the client-server architecture with multiple servers.
|
||||
|
||||
The communication always happens through the servers that the recepient defines, that manages simplex connections (see below).
|
||||
|
||||
Servers store messages only until delivered to the recepients, they do not store any message history or delivery log.
|
||||
|
||||
Servers do not have visibility of the message senders and message types, they only have visibility of connection address that is equal to the hash of public key used to encrypt messages sent into the connection (hash of receiver's public key).
|
||||
|
||||
|
||||
## Simplex connection - the main unit of network design
|
||||
|
||||
The network consists of multiple "simplex (non-duplex) connections". Access to each connection is controlled with unique (not shared with other connections) assymetric key pairs, separate for sender and the receiver. The sender and the receiver have private keys, and the server has associated public keys to verify participants.
|
||||
|
||||
The messages sent into the connection are encrypted and decrypted using another key pair - the recepient has the private key and the sender has the associated public key.
|
||||
|
||||
**Simplified simplex connection diagram:**
|
||||
|
||||

|
||||
|
||||
Connection is defined by sender's key 1 that is used to verify sender's messages and by receiver's key 2 that is used to verify subscription to messages (or requests to receive messages).
|
||||
|
||||
**Simplex connection operation:**
|
||||
|
||||

|
||||
|
||||
Sequence diagram does not show E2EE - connection itself knows nothing about encryption between sender and receiver.
|
||||
|
||||
|
||||
### Connections between user profiles
|
||||
|
||||
The system uses separate simplex connections (shown above) between user profiles (no information identifying users is stored on the servers) to send and to receive messages. Each connection is one-directional, from sender to recepient, without any correlation between connections and user profiles, and between 2 connections used to send and to receive messages to/from the same participants.
|
||||
|
||||
This design aims to reduce the risk of correlating communication of a user profile with other user profiles (to keep the list of connections private), and of correlating sent and received messages between the same participants.
|
||||
|
||||
Sockets used to send and receive connections can be used by an attacker for such correlation; to avoid it user profiles can be configured to use separate sockets for each one-directional connection (and in case of limited number of sockets, they can be closed and new sockets can be opened to avoid the possibility of any correlation, other than on network level). The probability of network level correlation could be further reduced with onion routing of messages (but it is not in scope).
|
||||
|
||||
Each one-directional connection is identified by two key pairs for each participant. Each connection participant registers on the server \<connection public key hash\> and \<server public key\> for each connection.
|
||||
|
||||
All keys are generated by the user. Server keys are used for communication between server and user (for server and user to sign and to encrypt). Connection keys are used for E2EE between users (for sender to encrypt using recepient's connection-specific public key and to sign using sender's connection-specific private key). For all keys, client app sends to all user's servers \<server public key\> and \<connection public key hash\>.
|
||||
|
||||
For each connection two keys allow to subscribe to receive messages from connection (by sending \<connection public key hash\> signed by \<server private key\> - server will be able to match it with connections database and accept such request to deliver messages from connection via the subscribed socket) and to decrypt server-side encryption of messages sent from (and separately encrypted by) connection using the \<server private key\>.
|
||||
|
||||
Each connection also allow another user to send messages to connection (by signing messages, including \<connection public key hash\>, with \<server private key\> - server will be able to match it with connections database and accept such messages) and to decrypt any service messages and responses using the \<server private key\>.
|
||||
|
||||
In this way the server will only have the list of connections via which messages can be sent or received (identified by \<connection public key hash\>), associated public keys to authenticate senders and recievers (\<server public key\>), with a separate server public key for each connection participant. The attacker will not be able to establish that any list of connections belongs to the same user (other than by analysing socket or network traffic).
|
||||
|
||||
|
||||
### Establishing connection between user profiles (two simplex connections)
|
||||
|
||||

|
||||
|
||||
|
||||
### Sending message and message receipt
|
||||
|
||||

|
||||
[1]: https://en.wikipedia.org/wiki/End-to-end_encryption
|
||||
[2]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
|
||||
[3]: https://en.wikipedia.org/wiki/Off-the-Record_Messaging
|
||||
[4]: https://en.wikipedia.org/wiki/Forward_secrecy
|
||||
[5]: https://mermaid-js.github.io/mermaid-live-editor
|
||||
[6]: edge-messaging.md
|
||||
[7]: edge-messaging-implementation.md
|
||||
[8]: graph-chat.md
|
||||
|
|