summaryrefslogtreecommitdiffstats
path: root/cmd/nymdrop-reader
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/nymdrop-reader')
-rw-r--r--cmd/nymdrop-reader/main.go108
-rw-r--r--cmd/nymdrop-reader/memguard_verify_test.go5
2 files changed, 52 insertions, 61 deletions
diff --git a/cmd/nymdrop-reader/main.go b/cmd/nymdrop-reader/main.go
index e2632cb..8e235d9 100644
--- a/cmd/nymdrop-reader/main.go
+++ b/cmd/nymdrop-reader/main.go
@@ -16,6 +16,7 @@ import (
"fmt"
"io"
"io/fs"
+ "nymdrop/internal/nym"
"os"
"os/exec"
"os/signal"
@@ -206,23 +207,33 @@ type wsReceived struct {
SenderTag string `json:"senderTag"`
}
-type wsSelfAddress struct {
- Type string `json:"type"`
- Address string `json:"address"`
-}
-
+// printSelfAddress queries the inbox address using nym-client's binary
+// protocol rather than JSON. This is deliberate, not cosmetic: nym-client
+// picks text-vs-binary for every later push ("received" events) based on
+// the format of the *last* request it saw on this connection (see
+// clients/native/src/websocket/handler.rs, ReceivedResponseType). Submission
+// payloads are raw binary crypto material with no base64 wrapping (see
+// Client.Send in internal/nym) — if this connection stayed in JSON/text
+// mode, nym-client would run String::from_utf8_lossy over that binary data
+// before handing it to us, silently corrupting it. Querying the self
+// address in binary form here locks the connection into binary mode for
+// the lifetime of receiveLoop below.
func printSelfAddress(ws *websocket.Conn) {
- req := map[string]string{"type": "selfAddress"}
- if err := websocket.JSON.Send(ws, req); err != nil {
+ if err := websocket.Message.Send(ws, nym.EncodeSelfAddressRequest()); err != nil {
fmt.Fprintf(os.Stderr, "selfAddress send: %v\n", err)
return
}
- var resp wsSelfAddress
- if err := websocket.JSON.Receive(ws, &resp); err != nil {
+ var frame []byte
+ if err := websocket.Message.Receive(ws, &frame); err != nil {
fmt.Fprintf(os.Stderr, "selfAddress recv: %v\n", err)
return
}
- fmt.Printf("Nym inbox address: %s\n\n", resp.Address)
+ addr, err := nym.DecodeSelfAddressResponse(frame)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "selfAddress decode: %v\n", err)
+ return
+ }
+ fmt.Printf("Nym inbox address: %s\n\n", addr)
}
// ── receive loop ──────────────────────────────────────────────────────────────
@@ -256,12 +267,12 @@ func receiveLoop(ws *websocket.Conn, privKey *ecdh.PrivateKey, outDir string) {
len(frame), frame[0], preview)
}
- dataB64, ok := extractMessageB64(frame)
+ raw, ok := extractMessage(frame)
if !ok {
continue
}
- plaintext, err := decryptMessage(dataB64, privKey)
+ plaintext, err := decryptMessage(raw, privKey)
if err != nil {
fmt.Fprintf(os.Stderr, "decrypt failed: %v\n", err)
continue
@@ -271,72 +282,53 @@ func receiveLoop(ws *websocket.Conn, privKey *ecdh.PrivateKey, outDir string) {
}
}
-// extractMessageB64 pulls the base64 payload string out of a nym-client
-// "received" frame. It tolerates the two encodings nym-client uses across
-// versions: text/JSON (the common case) and the native binary protocol.
-func extractMessageB64(frame []byte) (string, bool) {
+// extractMessage pulls the raw submission payload out of a nym-client
+// "received" frame, tolerating both encodings nym-client can use: binary
+// (what this connection actually runs in, see printSelfAddress) and
+// text/JSON (kept as a defensive fallback only — should never fire here).
+func extractMessage(frame []byte) ([]byte, bool) {
+ // Binary frame (nym native binary protocol), the expected/only path:
+ // 0x01 (Received tag) | has_sender_tag(1) | [sender_tag 16B] | msg_len(8, BE) | msg
+ // nym.DecodeReceived returns the message bytes exactly as the sender
+ // transmitted them — for nymdrop that is the raw, unencoded packet
+ // (ephemeral pubkey || iv || AES-GCM ciphertext), see internal/nym.Client.Send.
+ if frame[0] == nym.RespReceived {
+ return nym.DecodeReceived(frame)
+ }
+
// Text/JSON frame: {"type":"received","message":"<base64>","senderTag":...}
+ // Only reachable if this connection somehow reverted to text mode; the
+ // message field is base64 in that protocol (JSON strings can't carry
+ // arbitrary binary), so it must be decoded here to match the binary path.
if frame[0] == '{' {
var msg wsReceived
if err := json.Unmarshal(frame, &msg); err != nil || msg.Type != "received" {
- return "", false
+ return nil, false
}
- // message is normally a JSON string; tolerate the legacy
- // {"data":"<base64>"} object shape just in case.
var s string
- if err := json.Unmarshal(msg.Message, &s); err == nil {
- return s, true
- }
- var obj struct {
- Data string `json:"data"`
- }
- if err := json.Unmarshal(msg.Message, &obj); err == nil && obj.Data != "" {
- return obj.Data, true
+ if err := json.Unmarshal(msg.Message, &s); err != nil {
+ return nil, false
}
- return "", false
- }
-
- // Binary frame (nym native binary protocol):
- // 0x01 (Received tag) | senderTag flag(1) | [senderTag 16 bytes] | message
- // The message bytes are exactly what the sender transmitted — for nymdrop
- // that is the base64 ASCII string produced by the server.
- if frame[0] == 0x01 {
- buf := frame[1:]
- if len(buf) < 1 {
- return "", false
- }
- hasTag := buf[0]
- buf = buf[1:]
- if hasTag == 1 {
- if len(buf) < 16 {
- return "", false
- }
- buf = buf[16:]
- }
- if len(buf) == 0 {
- return "", false
+ raw, err := base64.StdEncoding.DecodeString(s)
+ if err != nil {
+ return nil, false
}
- return string(buf), true
+ return raw, true
}
- return "", false
+ return nil, false
}
// ── decryption ────────────────────────────────────────────────────────────────
-// decryptMessage decrypts a base64-encoded packet from the relay.
+// decryptMessage decrypts a raw packet from the relay.
//
// Wire format from relay:
// nonce_hex(32 ASCII chars) + ":" + ephemeral_x25519_pub(32) + iv(12) + ciphertext+tag
//
// The nonce prefix is stripped; it exists only to make identical submissions
// look different on the Nym wire. It is not used in decryption.
-func decryptMessage(dataB64 string, privKey *ecdh.PrivateKey) (string, error) {
- raw, err := base64.StdEncoding.DecodeString(dataB64)
- if err != nil {
- return "", fmt.Errorf("base64: %w", err)
- }
-
+func decryptMessage(raw []byte, privKey *ecdh.PrivateKey) (string, error) {
// Strip relay nonce prefix: 32 hex chars + ':'
const noncePrefix = 32 + 1 // "deadbeef...:" = 33 bytes
if len(raw) < noncePrefix+32+12+1 {
diff --git a/cmd/nymdrop-reader/memguard_verify_test.go b/cmd/nymdrop-reader/memguard_verify_test.go
index 3ab5fce..7b240e6 100644
--- a/cmd/nymdrop-reader/memguard_verify_test.go
+++ b/cmd/nymdrop-reader/memguard_verify_test.go
@@ -6,7 +6,6 @@ import (
"crypto/ecdh"
"crypto/rand"
"crypto/sha256"
- "encoding/base64"
"encoding/hex"
"io"
"testing"
@@ -53,9 +52,9 @@ func TestDecryptMessageAfterMemguard(t *testing.T) {
packet := append(append(append([]byte{}, ephPriv.PublicKey().Bytes()...), iv...), ciphertext...)
noncePrefix := hex.EncodeToString(make([]byte, 16)) + ":"
- dataB64 := base64.StdEncoding.EncodeToString(append([]byte(noncePrefix), packet...))
+ raw := append([]byte(noncePrefix), packet...)
- got, err := decryptMessage(dataB64, readerPriv)
+ got, err := decryptMessage(raw, readerPriv)
if err != nil {
t.Fatalf("decryptMessage: %v", err)
}