fixes step 2
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
yc
2026-04-12 13:38:15 +02:00
parent 793213b3fb
commit 327bd390c4
7 changed files with 186 additions and 111 deletions

View File

@@ -8,10 +8,12 @@ import (
"forge.redroom.link/yves/meowlib"
"forge.redroom.link/yves/meowlib/client"
"forge.redroom.link/yves/meowlib/client/invitation/messages"
"google.golang.org/protobuf/proto"
)
// Step2ReadAndAnswer reads an InvitationInitPayload from a .mwiv file, creates the
// invitee's peer entry, and writes the invitee's ContactCard response to a .mwiv file.
// invitee's peer entry, and writes the encrypted ContactCard (PackedUserMessage) to a
// .mwiv file for the initiator to pick up and process in step 3.
func Step2ReadAndAnswer(invitationFile string, nickname string, myNickname string, serverUids []string) error {
if _, err := os.Stat(invitationFile); os.IsNotExist(err) {
return err
@@ -33,12 +35,29 @@ func Step2ReadAndAnswer(invitationFile string, nickname string, myNickname strin
mynick = client.GetConfig().GetIdentity().Nickname
}
response, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(payload, nickname, mynick, serverUids)
packed, peer, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(payload, nickname, mynick, serverUids)
if err != nil {
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 {
return err
}
c := client.GetConfig()
filename := c.StoragePath + string(os.PathSeparator) + mynick + "-" + nickname + ".mwiv"
return response.GetMyContact().WriteCompressed(filename)
return os.WriteFile(filename, out, 0600)
}

View File

@@ -0,0 +1,142 @@
package messages_test
import (
"os"
"testing"
"forge.redroom.link/yves/meowlib"
"forge.redroom.link/yves/meowlib/client"
"forge.redroom.link/yves/meowlib/client/invitation/messages"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
// setupIdentity creates a fresh identity and sets it as the active config identity.
// Returns the identity and a cleanup function.
func setupIdentity(t *testing.T, nickname string) (*client.Identity, func()) {
t.Helper()
cfg := client.GetConfig()
cfg.SetMemPass("testpass") //nolint:errcheck
id, err := client.CreateIdentity(nickname)
require.NoError(t, err)
cfg.SetIdentity(id)
cleanup := func() {
os.RemoveAll(cfg.StoragePath + "/" + id.Uuid)
}
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) {
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)
require.NoError(t, err)
require.NotNil(t, payload)
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")
defer cleanInvitee()
packed, inviteePeer, err := messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard(
payload, "Alice", "Bob", nil,
)
require.NoError(t, err)
require.NotNil(t, packed, "step2 must return a PackedUserMessage, not just a peer")
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 ---
cfg.SetIdentity(initiator)
// Simulate how the server delivers the step-2 answer: marshal the PackedUserMessage
// into an Invitation.Payload.
packedBytes, err := proto.Marshal(packed)
require.NoError(t, err)
invitation := &meowlib.Invitation{
Uuid: payload.Uuid,
Step: 2,
From: inviteePeer.MyIdentity.Public,
Payload: packedBytes,
}
peer, myCC, err := messages.Step3InitiatorFinalizesInviteeAndCreatesContactCard(invitation)
require.NoError(t, err)
require.NotNil(t, peer)
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.
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)
}

View File

@@ -6,17 +6,28 @@ import (
)
// Step2InviteeCreatesInitiatorAndEncryptedContactCard creates the invitee's peer entry
// from an InvitationInitPayload and generates the encrypted ContactCard to be sent back
// to the initiator via any transport.
func Step2InviteeCreatesInitiatorAndEncryptedContactCard(payload *meowlib.InvitationInitPayload, nickname string, myNickname string, serverUids []string) (*client.Peer, error) {
// from an InvitationInitPayload, then builds the invitee's ContactCard and returns it
// as a PackedUserMessage asymmetrically encrypted with the initiator's temporary public
// key. The packed message is ready to be transmitted to the initiator via any transport
// (file, QR, server…); Step3InitiatorFinalizesInviteeAndCreatesContactCard on the
// initiator side will decrypt and process it.
func Step2InviteeCreatesInitiatorAndEncryptedContactCard(payload *meowlib.InvitationInitPayload, nickname string, myNickname string, serverUids []string) (*meowlib.PackedUserMessage, *client.Peer, error) {
mynick := myNickname
if mynick == "" {
mynick = client.GetConfig().GetIdentity().Nickname
}
peer, err := client.GetConfig().GetIdentity().InvitationStep2(mynick, nickname, serverUids, payload)
if err != nil {
return nil, err
return nil, nil, err
}
usermsg, err := peer.BuildInvitationStep2Message(peer.GetMyContact())
if err != nil {
return nil, nil, err
}
packed, err := peer.ProcessOutboundUserMessage(usermsg)
if err != nil {
return nil, nil, err
}
client.GetConfig().GetIdentity().Save()
return peer, nil
return packed, peer, nil
}

View File

@@ -62,29 +62,20 @@ func Step2ReadResponse(invitationData []byte, invitationServerUid string) (*meow
return meowlib.NewInvitationInitPayloadFromCompressed(serverMsg.Invitation.Payload)
}
// Step2PostAnswer builds and returns the packed server message that posts the
// invitee's ContactCard (encrypted with the initiator's temp key) to the invitation server.
func Step2PostAnswer(invitationId string, invitationServerUid string, timeout int) ([]byte, error) {
// Step2PostAnswer wraps the invitee's already-built PackedUserMessage into a server
// message and posts it to the invitation server. The packed message is produced by
// messages.Step2InviteeCreatesInitiatorAndEncryptedContactCard.
func Step2PostAnswer(invitationId string, packedMsg *meowlib.PackedUserMessage, invitationServerUid string, timeout int) ([]byte, error) {
peer := client.GetConfig().GetIdentity().Peers.GetFromInvitationId(invitationId)
if peer == nil {
return nil, errors.New("no peer with that invitation id")
}
answermsg, err := peer.BuildInvitationStep2Message(peer.GetMyContact())
if err != nil {
return nil, err
}
invitationServer, err := client.GetConfig().GetIdentity().MessageServers.LoadServer(invitationServerUid)
if err != nil {
return nil, err
}
packedMsg, err := peer.ProcessOutboundUserMessage(answermsg)
if err != nil {
return nil, err
}
toServerMessage, err := invitationServer.BuildToServerMessageInvitationAnswer(packedMsg, peer.MyIdentity.Public, invitationId, timeout)
if err != nil {
return nil, err