cleaner invitation step messages
This commit is contained in:
@@ -133,8 +133,12 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error
|
|||||||
}
|
}
|
||||||
// check if invitation answer (step-2 answer waiting for the initiator)
|
// check if invitation answer (step-2 answer waiting for the initiator)
|
||||||
if fromServerMessage.Invitation != nil {
|
if fromServerMessage.Invitation != nil {
|
||||||
peer, _, invErr := invmsgs.Step3InitiatorFinalizesInviteeAndCreatesContactCard(fromServerMessage.Invitation)
|
invBytes, marshalErr := proto.Marshal(fromServerMessage.Invitation)
|
||||||
if invErr == nil && peer != nil {
|
if marshalErr == nil {
|
||||||
|
step3Bytes, invErr := invmsgs.Step3InitiatorFinalizesInviteeAndCreatesContactCard(invBytes)
|
||||||
|
if invErr == nil && step3Bytes != nil {
|
||||||
|
peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(fromServerMessage.Invitation.Uuid)
|
||||||
|
if peer != nil {
|
||||||
// Auto-send step-3 CC to invitee's servers.
|
// Auto-send step-3 CC to invitee's servers.
|
||||||
msgs, sendErr := invsrv.Step3PostCard(peer.InvitationId)
|
msgs, sendErr := invsrv.Step3PostCard(peer.InvitationId)
|
||||||
if sendErr == nil {
|
if sendErr == nil {
|
||||||
@@ -146,6 +150,8 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Chat messages
|
// Chat messages
|
||||||
if len(fromServerMessage.Chat) > 0 {
|
if len(fromServerMessage.Chat) > 0 {
|
||||||
for _, packedUserMessage := range fromServerMessage.Chat {
|
for _, packedUserMessage := range fromServerMessage.Chat {
|
||||||
@@ -169,7 +175,9 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error
|
|||||||
|
|
||||||
// Handle invitation step 3: initiator's full ContactCard arriving at the invitee.
|
// Handle invitation step 3: initiator's full ContactCard arriving at the invitee.
|
||||||
if usermsg.Invitation != nil && usermsg.Invitation.Step == 3 {
|
if usermsg.Invitation != nil && usermsg.Invitation.Step == 3 {
|
||||||
finalizedPeer, finalErr := invmsgs.Step4InviteeFinalizesInitiator(usermsg)
|
invBytes, marshalErr := proto.Marshal(usermsg.Invitation)
|
||||||
|
if marshalErr == nil {
|
||||||
|
finalizedPeer, finalErr := invmsgs.Step4InviteeFinalizesInitiator(invBytes)
|
||||||
if finalErr == nil && finalizedPeer != nil {
|
if finalErr == nil && finalizedPeer != nil {
|
||||||
// Auto-send step-4 confirmation to initiator's servers.
|
// Auto-send step-4 confirmation to initiator's servers.
|
||||||
step4msgs, sendErr := invsrv.Step4PostConfirmation(finalizedPeer.InvitationId)
|
step4msgs, sendErr := invsrv.Step4PostConfirmation(finalizedPeer.InvitationId)
|
||||||
@@ -181,6 +189,7 @@ func ConsumeInboxFile(messageFilename string) ([]string, []string, string, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,28 +3,32 @@ package files
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"forge.redroom.link/yves/meowlib"
|
||||||
"forge.redroom.link/yves/meowlib/client"
|
"forge.redroom.link/yves/meowlib/client"
|
||||||
"forge.redroom.link/yves/meowlib/client/invitation/messages"
|
"forge.redroom.link/yves/meowlib/client/invitation/messages"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Step1Write creates a pending peer and writes the InvitationInitPayload to a file.
|
// Step1Write creates a pending peer and writes the InvitationInitPayload to a file.
|
||||||
// format: "qr" writes a QR-code PNG; anything else writes a compressed binary .mwiv file.
|
// format: "qr" writes a QR-code PNG; anything else writes a compressed binary .mwiv file.
|
||||||
func Step1Write(contactName string, myNickname string, invitationMessage string, serverUids []string, format string) (*client.Peer, error) {
|
func Step1Write(contactName string, myNickname string, invitationMessage string, serverUids []string, format string) error {
|
||||||
payload, peer, err := messages.Step1InitiatorCreatesInviteeAndTempKey(contactName, myNickname, invitationMessage, serverUids)
|
payloadBytes, err := messages.Step1InitiatorCreatesInviteeAndTempKey(contactName, myNickname, invitationMessage, serverUids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
|
}
|
||||||
|
var payload meowlib.InvitationInitPayload
|
||||||
|
if err := proto.Unmarshal(payloadBytes, &payload); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mynick := myNickname
|
||||||
|
if mynick == "" {
|
||||||
|
mynick = client.GetConfig().GetIdentity().Nickname
|
||||||
}
|
}
|
||||||
c := client.GetConfig()
|
c := client.GetConfig()
|
||||||
if format == "qr" {
|
if format == "qr" {
|
||||||
filename := c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".png"
|
filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + contactName + ".png"
|
||||||
if err := payload.WriteQr(filename); err != nil {
|
return payload.WriteQr(filename)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
} else {
|
filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + contactName + ".mwiv"
|
||||||
filename := c.StoragePath + string(os.PathSeparator) + peer.MyName + "-" + peer.Name + ".mwiv"
|
return payload.WriteCompressed(filename)
|
||||||
if err := payload.WriteCompressed(filename); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return peer, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Step2ReadAndAnswer reads an InvitationInitPayload from a .mwiv file, creates the
|
// Step2ReadAndAnswer reads an InvitationInitPayload from a .mwiv file, creates the
|
||||||
// invitee's peer entry, and writes the encrypted ContactCard (PackedUserMessage) to a
|
// invitee's peer entry, and writes the serialized Invitation (step=2) to a .mwiv file
|
||||||
// .mwiv file for the initiator to pick up and process in step 3.
|
// for the initiator to pick up and process in step 3.
|
||||||
func Step2ReadAndAnswer(invitationFile string, nickname string, myNickname string, serverUids []string) error {
|
func Step2ReadAndAnswer(invitationFile string, nickname string, myNickname string, serverUids []string) error {
|
||||||
if _, err := os.Stat(invitationFile); os.IsNotExist(err) {
|
if _, err := os.Stat(invitationFile); os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
@@ -29,35 +29,23 @@ func Step2ReadAndAnswer(invitationFile string, nickname string, myNickname strin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
payloadBytes, err := proto.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
mynick := myNickname
|
mynick := myNickname
|
||||||
if mynick == "" {
|
if mynick == "" {
|
||||||
mynick = client.GetConfig().GetIdentity().Nickname
|
mynick = client.GetConfig().GetIdentity().Nickname
|
||||||
}
|
}
|
||||||
|
|
||||||
packed, peer, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(payload, nickname, mynick, serverUids)
|
// messages.Step2 returns a serialized Invitation ready to write directly to file.
|
||||||
if err != nil {
|
invBytes, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(payloadBytes, nickname, mynick, serverUids)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap the PackedUserMessage in an Invitation so the initiator (step3) has the
|
|
||||||
// invitee's public key available for signature verification without an extra file.
|
|
||||||
packedBytes, err := proto.Marshal(packed)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
invitation := &meowlib.Invitation{
|
|
||||||
Uuid: peer.InvitationId,
|
|
||||||
Step: 2,
|
|
||||||
From: peer.MyIdentity.Public,
|
|
||||||
Payload: packedBytes,
|
|
||||||
}
|
|
||||||
out, err := proto.Marshal(invitation)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c := client.GetConfig()
|
c := client.GetConfig()
|
||||||
filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + nickname + ".mwiv"
|
filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + nickname + ".mwiv"
|
||||||
return os.WriteFile(filename, out, 0600)
|
return os.WriteFile(filename, invBytes, 0600)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,114 +29,108 @@ func setupIdentity(t *testing.T, nickname string) (*client.Identity, func()) {
|
|||||||
return id, cleanup
|
return id, cleanup
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestStep2ProducesPackedUserMessage verifies that Step2 returns a PackedUserMessage
|
// TestStep1ReturnsBinaryPayload verifies that Step1 returns non-empty bytes that
|
||||||
// (not just a peer) and that the message is encrypted with the initiator's temp key
|
// deserialise to a valid InvitationInitPayload, and that the pending peer is stored
|
||||||
// so Step3 can decrypt it.
|
// with only a temp keypair (no real identity keys yet).
|
||||||
func TestStep2ProducesPackedUserMessage(t *testing.T) {
|
func TestStep1ReturnsBinaryPayload(t *testing.T) {
|
||||||
cfg := client.GetConfig()
|
cfg := client.GetConfig()
|
||||||
cfg.SetMemPass("testpass") //nolint:errcheck
|
cfg.SetMemPass("testpass") //nolint:errcheck
|
||||||
|
|
||||||
// --- STEP 1: initiator creates temp keypair and payload ---
|
|
||||||
initiator, cleanInit := setupIdentity(t, "alice")
|
initiator, cleanInit := setupIdentity(t, "alice")
|
||||||
defer cleanInit()
|
defer cleanInit()
|
||||||
|
|
||||||
payload, initPeer, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "Hello!", nil)
|
step1Bytes, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "Hello!", nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, payload)
|
require.NotEmpty(t, step1Bytes)
|
||||||
|
|
||||||
|
var payload meowlib.InvitationInitPayload
|
||||||
|
require.NoError(t, proto.Unmarshal(step1Bytes, &payload))
|
||||||
|
assert.NotEmpty(t, payload.Uuid)
|
||||||
|
assert.NotEmpty(t, payload.PublicKey)
|
||||||
|
assert.Equal(t, "Alice", payload.Name)
|
||||||
|
assert.Equal(t, "Hello!", payload.InvitationMessage)
|
||||||
|
|
||||||
|
// Peer is saved with temp keypair only — no real identity keys yet.
|
||||||
|
initPeer := initiator.Peers.GetFromName("Bob")
|
||||||
require.NotNil(t, initPeer)
|
require.NotNil(t, initPeer)
|
||||||
// Initiator has only the temp keypair at this stage.
|
|
||||||
assert.Nil(t, initPeer.MyIdentity)
|
assert.Nil(t, initPeer.MyIdentity)
|
||||||
assert.NotNil(t, initPeer.InvitationKp)
|
assert.NotNil(t, initPeer.InvitationKp)
|
||||||
|
}
|
||||||
|
|
||||||
// --- STEP 2: invitee receives payload, creates peer, returns packed message ---
|
// TestFullInvitationFlow runs all four steps end-to-end, passing the binary output of
|
||||||
_, cleanInvitee := setupIdentity(t, "bob")
|
// each step directly to the next, and verifies that both peers end up with each other's
|
||||||
|
// real keys after the exchange completes.
|
||||||
|
func TestFullInvitationFlow(t *testing.T) {
|
||||||
|
cfg := client.GetConfig()
|
||||||
|
cfg.SetMemPass("testpass") //nolint:errcheck
|
||||||
|
|
||||||
|
// --- STEP 1: initiator creates temp keypair, gets binary payload ---
|
||||||
|
initiator, cleanInit := setupIdentity(t, "alice2")
|
||||||
|
defer cleanInit()
|
||||||
|
|
||||||
|
step1Bytes, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, step1Bytes)
|
||||||
|
|
||||||
|
// --- STEP 2: invitee creates peer, returns serialized Invitation (step=2) ---
|
||||||
|
invitee, cleanInvitee := setupIdentity(t, "bob2")
|
||||||
defer cleanInvitee()
|
defer cleanInvitee()
|
||||||
|
|
||||||
packed, inviteePeer, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(
|
step2Bytes, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(step1Bytes, "Alice", "Bob", nil)
|
||||||
payload, "Alice", "Bob", nil,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, packed, "step2 must return a PackedUserMessage, not just a peer")
|
require.NotEmpty(t, step2Bytes, "step2 must return non-empty invitation bytes")
|
||||||
|
|
||||||
|
// Invitee now has a peer with full keypairs.
|
||||||
|
inviteePeer := invitee.Peers.GetFromName("Alice")
|
||||||
require.NotNil(t, inviteePeer)
|
require.NotNil(t, inviteePeer)
|
||||||
|
|
||||||
// The packed message destination is the initiator's temp key (used as lookup key).
|
|
||||||
assert.Equal(t, payload.PublicKey, packed.Destination)
|
|
||||||
|
|
||||||
// The invitee peer has full keypairs now.
|
|
||||||
assert.NotNil(t, inviteePeer.MyIdentity)
|
assert.NotNil(t, inviteePeer.MyIdentity)
|
||||||
assert.NotNil(t, inviteePeer.MyEncryptionKp)
|
assert.NotNil(t, inviteePeer.MyEncryptionKp)
|
||||||
assert.NotNil(t, inviteePeer.MyLookupKp)
|
assert.NotNil(t, inviteePeer.MyLookupKp)
|
||||||
|
|
||||||
// --- STEP 3: initiator decrypts invitee's packed message and finalises ---
|
// The step-2 wire format is a serialized Invitation.
|
||||||
|
var inv2 meowlib.Invitation
|
||||||
|
require.NoError(t, proto.Unmarshal(step2Bytes, &inv2))
|
||||||
|
assert.EqualValues(t, 2, inv2.Step)
|
||||||
|
assert.NotEmpty(t, inv2.Uuid)
|
||||||
|
assert.Equal(t, inviteePeer.MyIdentity.Public, inv2.From)
|
||||||
|
assert.NotEmpty(t, inv2.Payload)
|
||||||
|
|
||||||
|
// --- STEP 3: initiator decrypts invitee's card, returns serialized Invitation (step=3) ---
|
||||||
cfg.SetIdentity(initiator)
|
cfg.SetIdentity(initiator)
|
||||||
|
|
||||||
// Simulate how the server delivers the step-2 answer: marshal the PackedUserMessage
|
step3Bytes, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(step2Bytes)
|
||||||
// into an Invitation.Payload.
|
|
||||||
packedBytes, err := proto.Marshal(packed)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, step3Bytes)
|
||||||
|
|
||||||
invitation := &meowlib.Invitation{
|
// Initiator's peer must now hold invitee's real keys; temp keypair must be gone.
|
||||||
Uuid: payload.Uuid,
|
initPeer := initiator.Peers.GetFromName("Bob")
|
||||||
Step: 2,
|
require.NotNil(t, initPeer)
|
||||||
From: inviteePeer.MyIdentity.Public,
|
assert.Equal(t, inviteePeer.MyIdentity.Public, initPeer.ContactPublicKey)
|
||||||
Payload: packedBytes,
|
assert.Equal(t, inviteePeer.MyEncryptionKp.Public, initPeer.ContactEncryption)
|
||||||
}
|
assert.Equal(t, inviteePeer.MyLookupKp.Public, initPeer.ContactLookupKey)
|
||||||
|
assert.Nil(t, initPeer.InvitationKp, "temp keypair must be cleared after step3")
|
||||||
|
assert.NotEmpty(t, initPeer.DrKpPublic)
|
||||||
|
assert.NotEmpty(t, initPeer.DrRootKey)
|
||||||
|
|
||||||
peer, myCC, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(invitation)
|
// The step-3 wire format is a serialized Invitation.
|
||||||
|
var inv3 meowlib.Invitation
|
||||||
|
require.NoError(t, proto.Unmarshal(step3Bytes, &inv3))
|
||||||
|
assert.EqualValues(t, 3, inv3.Step)
|
||||||
|
assert.NotEmpty(t, inv3.Uuid)
|
||||||
|
assert.NotEmpty(t, inv3.Payload)
|
||||||
|
|
||||||
|
// --- STEP 4: invitee finalises initiator ---
|
||||||
|
cfg.SetIdentity(invitee)
|
||||||
|
|
||||||
|
finalPeer, err := messages.Step4InviteeFinalizesInitiator(step3Bytes)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, peer)
|
require.NotNil(t, finalPeer)
|
||||||
require.NotNil(t, myCC, "step3 must produce the initiator's ContactCard to send to invitee")
|
|
||||||
|
|
||||||
// Initiator's peer must now hold invitee's real keys.
|
// Invitee's peer must now hold initiator's real keys and the invitation must be complete.
|
||||||
assert.Equal(t, inviteePeer.MyIdentity.Public, peer.ContactPublicKey)
|
assert.Equal(t, initPeer.MyIdentity.Public, finalPeer.ContactPublicKey)
|
||||||
assert.Equal(t, inviteePeer.MyEncryptionKp.Public, peer.ContactEncryption)
|
assert.Equal(t, initPeer.MyEncryptionKp.Public, finalPeer.ContactEncryption)
|
||||||
assert.Equal(t, inviteePeer.MyLookupKp.Public, peer.ContactLookupKey)
|
assert.Equal(t, initPeer.MyLookupKp.Public, finalPeer.ContactLookupKey)
|
||||||
assert.Nil(t, peer.InvitationKp, "temp keypair must be cleared after step3")
|
assert.False(t, finalPeer.InvitationPending(), "invitation must be fully finalized")
|
||||||
assert.NotEmpty(t, myCC.DrRootKey)
|
assert.NotEmpty(t, finalPeer.DrRootKey)
|
||||||
assert.NotEmpty(t, myCC.DrPublicKey)
|
assert.NotEmpty(t, finalPeer.ContactDrPublicKey)
|
||||||
}
|
|
||||||
|
|
||||||
// TestStep2Step3RoundTripPayload verifies that the PackedUserMessage produced by step2
|
|
||||||
// actually carries the invitee's ContactCard when decrypted by the initiator.
|
|
||||||
func TestStep2Step3RoundTripPayload(t *testing.T) {
|
|
||||||
cfg := client.GetConfig()
|
|
||||||
cfg.SetMemPass("testpass") //nolint:errcheck
|
|
||||||
|
|
||||||
initiator, cleanInit := setupIdentity(t, "alice2")
|
|
||||||
defer cleanInit()
|
|
||||||
|
|
||||||
payload, _, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, cleanInvitee := setupIdentity(t, "bob2")
|
|
||||||
defer cleanInvitee()
|
|
||||||
|
|
||||||
packed, inviteePeer, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(payload, "Alice", "Bob", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Confirm the message serialises cleanly (transport simulation).
|
|
||||||
packedBytes, err := proto.Marshal(packed)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotEmpty(t, packedBytes)
|
|
||||||
|
|
||||||
// Switch back to initiator and run step3.
|
|
||||||
cfg.SetIdentity(initiator)
|
|
||||||
|
|
||||||
var roundTripped meowlib.PackedUserMessage
|
|
||||||
require.NoError(t, proto.Unmarshal(packedBytes, &roundTripped))
|
|
||||||
|
|
||||||
invitation := &meowlib.Invitation{
|
|
||||||
Uuid: payload.Uuid,
|
|
||||||
Step: 2,
|
|
||||||
From: inviteePeer.MyIdentity.Public,
|
|
||||||
Payload: packedBytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, myCC, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(invitation)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, myCC)
|
|
||||||
|
|
||||||
// The initiator's CC must reference the invitee's invitation ID so the invitee
|
|
||||||
// can match it when step4 arrives.
|
|
||||||
assert.Equal(t, payload.Uuid, myCC.InvitationId)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
package messages
|
package messages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"forge.redroom.link/yves/meowlib"
|
|
||||||
"forge.redroom.link/yves/meowlib/client"
|
"forge.redroom.link/yves/meowlib/client"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Step1InitiatorCreatesInviteeAndTempKey creates a minimal pending peer and a temporary
|
// Step1InitiatorCreatesInviteeAndTempKey creates a minimal pending peer and a temporary
|
||||||
// keypair, and returns the InvitationInitPayload to be transmitted to the invitee
|
// keypair, and returns the serialized InvitationInitPayload bytes to be transmitted to
|
||||||
// via any transport (file, QR, server…).
|
// the invitee via any transport (file, QR, server…). The peer is already persisted by
|
||||||
func Step1InitiatorCreatesInviteeAndTempKey(contactName string, myNickname string, invitationMessage string, serverUids []string) (*meowlib.InvitationInitPayload, *client.Peer, error) {
|
// InvitationStep1 so no peer reference is returned.
|
||||||
|
func Step1InitiatorCreatesInviteeAndTempKey(contactName string, myNickname string, invitationMessage string, serverUids []string) ([]byte, error) {
|
||||||
mynick := myNickname
|
mynick := myNickname
|
||||||
if mynick == "" {
|
if mynick == "" {
|
||||||
mynick = client.GetConfig().GetIdentity().Nickname
|
mynick = client.GetConfig().GetIdentity().Nickname
|
||||||
}
|
}
|
||||||
payload, peer, err := client.GetConfig().GetIdentity().InvitationStep1(mynick, contactName, serverUids, invitationMessage)
|
payload, _, err := client.GetConfig().GetIdentity().InvitationStep1(mynick, contactName, serverUids, invitationMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
client.GetConfig().GetIdentity().Save()
|
client.GetConfig().GetIdentity().Save()
|
||||||
return payload, peer, nil
|
return proto.Marshal(payload)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,31 +3,45 @@ package messages
|
|||||||
import (
|
import (
|
||||||
"forge.redroom.link/yves/meowlib"
|
"forge.redroom.link/yves/meowlib"
|
||||||
"forge.redroom.link/yves/meowlib/client"
|
"forge.redroom.link/yves/meowlib/client"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Step2InviteeCreatesInitiatorAndEncryptedContactCard creates the invitee's peer entry
|
// Step2InviteeCreatesInitiatorAndEncryptedContactCard deserialises the step-1 payload bytes,
|
||||||
// from an InvitationInitPayload, then builds the invitee's ContactCard and returns it
|
// creates the invitee's peer entry, builds and encrypts the invitee's ContactCard, and returns
|
||||||
// as a PackedUserMessage asymmetrically encrypted with the initiator's temporary public
|
// a serialized Invitation (step=2) whose Payload is the PackedUserMessage encrypted with the
|
||||||
// key. The packed message is ready to be transmitted to the initiator via any transport
|
// initiator's temporary public key. The bytes are transport-ready and consumed directly by
|
||||||
// (file, QR, server…); Step3InitiatorFinalizesInviteeAndCreatesContactCard on the
|
// Step3InitiatorFinalizesInviteeAndCreatesContactCard.
|
||||||
// initiator side will decrypt and process it.
|
func Step2InviteeCreatesInitiatorAndEncryptedContactCard(payloadBytes []byte, nickname string, myNickname string, serverUids []string) ([]byte, error) {
|
||||||
func Step2InviteeCreatesInitiatorAndEncryptedContactCard(payload *meowlib.InvitationInitPayload, nickname string, myNickname string, serverUids []string) (*meowlib.PackedUserMessage, *client.Peer, error) {
|
|
||||||
mynick := myNickname
|
mynick := myNickname
|
||||||
if mynick == "" {
|
if mynick == "" {
|
||||||
mynick = client.GetConfig().GetIdentity().Nickname
|
mynick = client.GetConfig().GetIdentity().Nickname
|
||||||
}
|
}
|
||||||
peer, err := client.GetConfig().GetIdentity().InvitationStep2(mynick, nickname, serverUids, payload)
|
var payload meowlib.InvitationInitPayload
|
||||||
|
if err := proto.Unmarshal(payloadBytes, &payload); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
peer, err := client.GetConfig().GetIdentity().InvitationStep2(mynick, nickname, serverUids, &payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
usermsg, err := peer.BuildInvitationStep2Message(peer.GetMyContact())
|
usermsg, err := peer.BuildInvitationStep2Message(peer.GetMyContact())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
packed, err := peer.ProcessOutboundUserMessage(usermsg)
|
packed, err := peer.ProcessOutboundUserMessage(usermsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
|
}
|
||||||
|
packedBytes, err := proto.Marshal(packed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inv := &meowlib.Invitation{
|
||||||
|
Uuid: payload.Uuid,
|
||||||
|
Step: 2,
|
||||||
|
From: peer.MyIdentity.Public,
|
||||||
|
Payload: packedBytes,
|
||||||
}
|
}
|
||||||
client.GetConfig().GetIdentity().Save()
|
client.GetConfig().GetIdentity().Save()
|
||||||
return packed, peer, nil
|
return proto.Marshal(inv)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,39 +8,55 @@ import (
|
|||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Step3InitiatorFinalizesInviteeAndCreatesContactCard is called by the initiator when a
|
// Step3InitiatorFinalizesInviteeAndCreatesContactCard is called by the initiator when the
|
||||||
// step-2 answer (invitee's encrypted ContactCard) arrives. It decrypts the card, upgrades
|
// step-2 answer (serialized Invitation bytes) arrives. It decrypts the invitee's ContactCard,
|
||||||
// the invitee's peer entry with the real keys, and returns the initiator's own ContactCard
|
// upgrades the pending peer with the invitee's real keys, and returns a serialized Invitation
|
||||||
// ready to be sent to the invitee via any transport.
|
// (step=3) whose Payload is the initiator's ContactCard, ready to be consumed directly by
|
||||||
func Step3InitiatorFinalizesInviteeAndCreatesContactCard(invitation *meowlib.Invitation) (*client.Peer, *meowlib.ContactCard, error) {
|
// Step4InviteeFinalizesInitiator on the invitee side.
|
||||||
|
func Step3InitiatorFinalizesInviteeAndCreatesContactCard(invitationBytes []byte) ([]byte, error) {
|
||||||
|
var invitation meowlib.Invitation
|
||||||
|
if err := proto.Unmarshal(invitationBytes, &invitation); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var invitationAnswer meowlib.PackedUserMessage
|
var invitationAnswer meowlib.PackedUserMessage
|
||||||
if err := proto.Unmarshal(invitation.Payload, &invitationAnswer); err != nil {
|
if err := proto.Unmarshal(invitation.Payload, &invitationAnswer); err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(invitation.Uuid)
|
peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(invitation.Uuid)
|
||||||
if peer == nil {
|
if peer == nil {
|
||||||
return nil, nil, errors.New("no peer for invitation uuid " + invitation.Uuid)
|
return nil, errors.New("no peer for invitation uuid " + invitation.Uuid)
|
||||||
}
|
}
|
||||||
// Guard against duplicate delivery (e.g., same answer from multiple servers).
|
// Guard against duplicate delivery (e.g., same answer from multiple servers).
|
||||||
if peer.InvitationKp == nil {
|
if peer.InvitationKp == nil {
|
||||||
return nil, nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
usermsg, err := peer.ProcessInboundStep2UserMessage(&invitationAnswer, invitation.From)
|
usermsg, err := peer.ProcessInboundStep2UserMessage(&invitationAnswer, invitation.From)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var inviteeCC meowlib.ContactCard
|
var inviteeCC meowlib.ContactCard
|
||||||
if err := proto.Unmarshal(usermsg.Invitation.Payload, &inviteeCC); err != nil {
|
if err := proto.Unmarshal(usermsg.Invitation.Payload, &inviteeCC); err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
myCC, peer, err := client.GetConfig().GetIdentity().InvitationStep3(&inviteeCC)
|
myCC, _, err := client.GetConfig().GetIdentity().InvitationStep3(&inviteeCC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
client.GetConfig().GetIdentity().Save()
|
client.GetConfig().GetIdentity().Save()
|
||||||
return peer, myCC, nil
|
|
||||||
|
ccBytes, err := proto.Marshal(myCC)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inv := &meowlib.Invitation{
|
||||||
|
Uuid: myCC.InvitationId,
|
||||||
|
Step: 3,
|
||||||
|
Payload: ccBytes,
|
||||||
|
}
|
||||||
|
return proto.Marshal(inv)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,25 @@
|
|||||||
package messages
|
package messages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
|
|
||||||
"forge.redroom.link/yves/meowlib"
|
"forge.redroom.link/yves/meowlib"
|
||||||
"forge.redroom.link/yves/meowlib/client"
|
"forge.redroom.link/yves/meowlib/client"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Step4InviteeFinalizesInitiator is called by the invitee's message processor when a
|
// Step4InviteeFinalizesInitiator is called by the invitee when the step-3 answer
|
||||||
// UserMessage with invitation.step == 3 arrives. It unmarshals the initiator's ContactCard
|
// (serialized Invitation bytes) arrives. It unmarshals the initiator's ContactCard and
|
||||||
// and completes the invitee's peer entry with the initiator's real keys.
|
// completes the invitee's peer entry with the initiator's real keys.
|
||||||
func Step4InviteeFinalizesInitiator(usermsg *meowlib.UserMessage) (*client.Peer, error) {
|
func Step4InviteeFinalizesInitiator(invitationBytes []byte) (*client.Peer, error) {
|
||||||
if usermsg.Invitation == nil || usermsg.Invitation.Step != 3 {
|
var inv meowlib.Invitation
|
||||||
return nil, errors.New("expected invitation step 3")
|
if err := proto.Unmarshal(invitationBytes, &inv); err != nil {
|
||||||
}
|
return nil, err
|
||||||
var initiatorCC meowlib.ContactCard
|
}
|
||||||
if err := proto.Unmarshal(usermsg.Invitation.Payload, &initiatorCC); err != nil {
|
var initiatorCC meowlib.ContactCard
|
||||||
|
if err := proto.Unmarshal(inv.Payload, &initiatorCC); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Patch the invitation ID from the outer message in case it was not set in the CC.
|
|
||||||
if initiatorCC.InvitationId == "" {
|
if initiatorCC.InvitationId == "" {
|
||||||
initiatorCC.InvitationId = usermsg.Invitation.Uuid
|
initiatorCC.InvitationId = inv.Uuid
|
||||||
}
|
}
|
||||||
if err := client.GetConfig().GetIdentity().InvitationStep4(&initiatorCC); err != nil {
|
if err := client.GetConfig().GetIdentity().InvitationStep4(&initiatorCC); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
@startuml General Invitation Steps
|
@startuml General Invitation Steps
|
||||||
InitiatingUser -> InitiatingUser : STEP_1 = Create InivitedUser_Id generate a public key, invitation uid & message for InvitedUser optionnally password protected
|
InitiatingUser -> InitiatingUser : STEP_1 = Create InivitedUser_Id generate a public key, invitation uid & message for InvitedUser optionnally password protected
|
||||||
|
note right of InitiatingUser #Yellow: Invitee created, only temp key
|
||||||
InitiatingUser -> InvitedUser: STEP_1_SEND= transmit step 1 data (QR Code, Bluetooth, messaging, mail, ...) optional password being tranmitted through another channel
|
InitiatingUser -> InvitedUser: STEP_1_SEND= transmit step 1 data (QR Code, Bluetooth, messaging, mail, ...) optional password being tranmitted through another channel
|
||||||
|
|
||||||
InvitedUser -> InvitedUser :Create InitatingUser_Id & InvitedUser ContactCard
|
InvitedUser -> InvitedUser :Create InitatingUser_Id & InvitedUser ContactCard
|
||||||
|
note right of InvitedUser #Yellow: Initiator created, empty
|
||||||
InvitedUser -> InitiatingUser: STEP_2_SEND=transmit InvitedUser ContactCard (QR Codes, Bluetooth, messaging, mail, ...) encrypted with initiating user pub key
|
InvitedUser -> InitiatingUser: STEP_2_SEND=transmit InvitedUser ContactCard (QR Codes, Bluetooth, messaging, mail, ...) encrypted with initiating user pub key
|
||||||
|
|
||||||
InitiatingUser -> InitiatingUser : STEP_3=InitiatingUser_Id Accept Invitation and create answer (Generate InitiatingUser ContactCard and create finalized InvitedUser contact)
|
InitiatingUser -> InitiatingUser : STEP_3=InitiatingUser_Id Accept Invitation and create answer (Generate InitiatingUser ContactCard and create finalized InvitedUser contact)
|
||||||
|
note right of InitiatingUser #Lime: Invitee complete
|
||||||
InitiatingUser -> InvitedUser: STEP_3_SEND=Send answer through invited user's message servers from contact card
|
InitiatingUser -> InvitedUser: STEP_3_SEND=Send answer through invited user's message servers from contact card
|
||||||
|
|
||||||
InvitedUser -> InvitedUser : Finalize InitiatingUser from its ContactCard
|
InvitedUser -> InvitedUser : Finalize InitiatingUser from its ContactCard
|
||||||
|
note right of InvitedUser #Lime: Initiator complete
|
||||||
|
|
||||||
InvitedUser -> InitiatingUser: STEP_4= Send confirmation to InitiatingUser that communication is possible through initiating user's message servers from contact card
|
InvitedUser -> InitiatingUser: STEP_4= Send confirmation to InitiatingUser that communication is possible through initiating user's message servers from contact card
|
||||||
@enduml
|
@enduml
|
||||||
Reference in New Issue
Block a user