diff options
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/nymdrop-reader/main.go | 108 | ||||
| -rw-r--r-- | cmd/nymdrop-reader/memguard_verify_test.go | 5 | ||||
| -rw-r--r-- | cmd/nymdrop/main.go | 6 |
3 files changed, 57 insertions, 62 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) } diff --git a/cmd/nymdrop/main.go b/cmd/nymdrop/main.go index c2090ce..0ae6acc 100644 --- a/cmd/nymdrop/main.go +++ b/cmd/nymdrop/main.go @@ -33,7 +33,11 @@ func main() { if *dryRun { nymClient = nym.NewDryRun(*journalistAddr) } else { - nymClient = nym.New(*journalistAddr, nymBin) + var err error + nymClient, err = nym.New(*journalistAddr, nymBin) + if err != nil { + log.Fatalf("nym client: %v", err) + } } if err := nymClient.Start(); err != nil { log.Fatalf("nym client: %v", err) |
