mobile: derive the encryption context as the maintainer's core does

The core now picks the KDF context with pickSessionContext: --url, else
the URL of the highest-priority transport, leaving out cupsonline (its
room list only exists once the exit is up) and the "http://#" placeholder,
else "http://#". The bridge still let cupsonline in, and a classic channel
without a document (oneme, direct) fell back to the transport's name, so
the phone and a CLI peer derived different keys.
This commit is contained in:
meepo161
2026-09-27 04:34:48 +03:00
parent a6231f1f0d
commit 7aa5977c39
4 changed files with 56 additions and 24 deletions
+4 -4
View File
@@ -152,11 +152,11 @@ func classicTransport(transportType, documentURL, encryptionSecret, codec, maxTo
appendLog("[ANDROID] Шифрование транспорта отключено (ключ не задан)")
return inner, nil
}
// Same fallback as the CLI: the KDF context is the document URL, or
// the transport name when there isn't one (oneme). Both peers must
// derive the same context or the encrypted channel just won't work.
// Same context as the core: the document URL, or "http://#" when there
// isn't one (oneme, direct). Both peers must derive the same context or
// the encrypted channel just won't work.
encrypted, err := transport.NewEncryptedTransport(inner, encryptionSecret,
classicContext(transportType, documentURL), exit)
classicContext(documentURL), exit)
if err != nil {
return nil, err
}
+20 -10
View File
@@ -3,7 +3,6 @@ package mobile
import (
"encoding/json"
"fmt"
"sort"
"openflux/transport"
"openflux/transport/manager"
@@ -121,16 +120,27 @@ func buildSessionWith(specsJSON, secret string, exit bool) (transport.Transport,
return demux, sess, nil
}
// sessionContext is the encryption context. The exit derives it from its
// --url, so it is the document URL of the highest-priority transport that
// has one; "http://#" matches the CLI's --url default when none has.
// sessionContext is the encryption context, derived as the core's
// pickSessionContext does for an exit without --url: the document URL of the
// highest-priority transport that has one, cupsonline aside (its room list
// only exists once the exit is up), else "http://#". Equal priorities keep
// the first transport, as the core does.
func sessionContext(specs []sessionSpec) string {
sorted := append([]sessionSpec(nil), specs...)
sort.SliceStable(sorted, func(i, j int) bool { return sorted[i].Priority > sorted[j].Priority })
for _, s := range sorted {
if s.URL != "" {
return s.URL
best := -1
for i, s := range specs {
if s.Type == "cupsonline" || s.URL == "" || s.URL == placeholderURL {
continue
}
if best < 0 || s.Priority > specs[best].Priority {
best = i
}
}
return "http://#"
if best >= 0 {
return specs[best].URL
}
return placeholderURL
}
// placeholderURL is the core's --url default, the context of a channel that
// has no document URL.
const placeholderURL = "http://#"
+24
View File
@@ -77,3 +77,27 @@ func TestSessionContextPrefersHighestPriorityURL(t *testing.T) {
t.Fatalf("context without URLs = %q", got)
}
}
// The exit creates the cupsonline room list only when it starts, so the
// core leaves it out of the context; the phone must too.
func TestSessionContextSkipsCupsonline(t *testing.T) {
specs := []sessionSpec{
{Name: "cupsonline", Type: "cupsonline", URL: "room-list", Priority: 100},
{Name: "yandex", Type: "yandex", URL: "https://docs.example/d", Priority: 50},
}
if got := sessionContext(specs); got != "https://docs.example/d" {
t.Fatalf("context = %q", got)
}
if got := sessionContext(specs[:1]); got != "http://#" {
t.Fatalf("context of a cupsonline-only session = %q", got)
}
}
func TestClassicContextFallsBackToPlaceholder(t *testing.T) {
if got := classicContext(""); got != "http://#" {
t.Fatalf("context without a document = %q", got)
}
if got := classicContext("https://docs.example/d"); got != "https://docs.example/d" {
t.Fatalf("context = %q", got)
}
}
+8 -10
View File
@@ -56,7 +56,7 @@ func ExitShareLink(host, name string) (string, error) {
func exitShareClassic(transportType, documentURL, secret, codec string) *share.Config {
c := &share.Config{
Secret: secret,
Context: classicContext(transportType, documentURL),
Context: classicContext(documentURL),
Transports: []share.Transport{{Type: transportType, URL: documentURL}},
}
if codec == "legacy" {
@@ -96,14 +96,12 @@ func exitShareSession(specsJSON, secret string) *share.Config {
return c
}
// classicContext is the classic mode's encryption context, as the CLI and
// classicTransport derive it.
func classicContext(transportType, documentURL string) string {
if documentURL != "" {
return documentURL
// classicContext is the classic mode's encryption context, as the core
// derives it for --transport with --url: the document URL, or "http://#"
// when there is none (oneme, direct).
func classicContext(documentURL string) string {
if documentURL == "" {
return placeholderURL
}
if transportType == "" {
return "yandex"
}
return transportType
return documentURL
}