cleaner invitation step messages

This commit is contained in:
yc
2026-04-14 19:12:09 +02:00
parent 327bd390c4
commit 00e4e6b046
9 changed files with 208 additions and 179 deletions
+78 -84
View File
@@ -29,114 +29,108 @@ func setupIdentity(t *testing.T, nickname string) (*client.Identity, func()) {
return id, cleanup
}
// TestStep2ProducesPackedUserMessage verifies that Step2 returns a PackedUserMessage
// (not just a peer) and that the message is encrypted with the initiator's temp key
// so Step3 can decrypt it.
func TestStep2ProducesPackedUserMessage(t *testing.T) {
// TestStep1ReturnsBinaryPayload verifies that Step1 returns non-empty bytes that
// deserialise to a valid InvitationInitPayload, and that the pending peer is stored
// with only a temp keypair (no real identity keys yet).
func TestStep1ReturnsBinaryPayload(t *testing.T) {
cfg := client.GetConfig()
cfg.SetMemPass("testpass") //nolint:errcheck
// --- STEP 1: initiator creates temp keypair and payload ---
initiator, cleanInit := setupIdentity(t, "alice")
defer cleanInit()
payload, initPeer, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "Hello!", nil)
step1Bytes, err := messages.Step1InitiatorCreatesInviteeAndTempKey("Bob", "Alice", "Hello!", nil)
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)
// Initiator has only the temp keypair at this stage.
assert.Nil(t, initPeer.MyIdentity)
assert.NotNil(t, initPeer.InvitationKp)
}
// --- STEP 2: invitee receives payload, creates peer, returns packed message ---
_, cleanInvitee := setupIdentity(t, "bob")
// TestFullInvitationFlow runs all four steps end-to-end, passing the binary output of
// 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()
packed, inviteePeer, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(
payload, "Alice", "Bob", nil,
)
step2Bytes, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(step1Bytes, "Alice", "Bob", nil)
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)
// 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.MyEncryptionKp)
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)
// Simulate how the server delivers the step-2 answer: marshal the PackedUserMessage
// into an Invitation.Payload.
packedBytes, err := proto.Marshal(packed)
step3Bytes, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(step2Bytes)
require.NoError(t, err)
require.NotEmpty(t, step3Bytes)
invitation := &meowlib.Invitation{
Uuid: payload.Uuid,
Step: 2,
From: inviteePeer.MyIdentity.Public,
Payload: packedBytes,
}
// Initiator's peer must now hold invitee's real keys; temp keypair must be gone.
initPeer := initiator.Peers.GetFromName("Bob")
require.NotNil(t, initPeer)
assert.Equal(t, inviteePeer.MyIdentity.Public, initPeer.ContactPublicKey)
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.NotNil(t, peer)
require.NotNil(t, myCC, "step3 must produce the initiator's ContactCard to send to invitee")
require.NotNil(t, finalPeer)
// Initiator's peer must now hold invitee's real keys.
assert.Equal(t, inviteePeer.MyIdentity.Public, peer.ContactPublicKey)
assert.Equal(t, inviteePeer.MyEncryptionKp.Public, peer.ContactEncryption)
assert.Equal(t, inviteePeer.MyLookupKp.Public, peer.ContactLookupKey)
assert.Nil(t, peer.InvitationKp, "temp keypair must be cleared after step3")
assert.NotEmpty(t, myCC.DrRootKey)
assert.NotEmpty(t, myCC.DrPublicKey)
}
// 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)
// Invitee's peer must now hold initiator's real keys and the invitation must be complete.
assert.Equal(t, initPeer.MyIdentity.Public, finalPeer.ContactPublicKey)
assert.Equal(t, initPeer.MyEncryptionKp.Public, finalPeer.ContactEncryption)
assert.Equal(t, initPeer.MyLookupKp.Public, finalPeer.ContactLookupKey)
assert.False(t, finalPeer.InvitationPending(), "invitation must be fully finalized")
assert.NotEmpty(t, finalPeer.DrRootKey)
assert.NotEmpty(t, finalPeer.ContactDrPublicKey)
}