refactor: rename trustAllCerts → usePinnedTrustStore

The field no longer means 'trust all certificates' — since the Samsung
One UI 7 fix, it means 'use the pinned trust store (hardcoded +
TOFU-pinned certs + system CAs) instead of system-only trust.'

The old name was actively misleading and could cause a future
contributor to interpret the flag literally, potentially reintroducing
the trust-all pattern that Samsung Knox and GrapheneOS reject.

Renamed across all 4 files:
- ElectrumxServer.kt: field definition + updated KDoc
- ElectrumXClient.kt: createSocket() usage + comments
- NamecoinSettings.kt: parseServerString() usage + comments
- NamecoinSettingsTest.kt: test assertions
This commit is contained in:
M
2026-03-26 06:33:43 +11:00
parent 7f451ee7c7
commit 2e02edc333
4 changed files with 27 additions and 27 deletions
@@ -66,7 +66,7 @@ data class NamecoinSettings(
* TLS is the default protocol. Append `:tcp` for plaintext * TLS is the default protocol. Append `:tcp` for plaintext
* (useful for `.onion` addresses and local servers). * (useful for `.onion` addresses and local servers).
* *
* `.onion` addresses automatically get `trustAllCerts = true` * `.onion` addresses automatically get `usePinnedTrustStore = true`
* since certificate verification is meaningless over Tor. * since certificate verification is meaningless over Tor.
*/ */
fun parseServerString(s: String): ElectrumxServer? { fun parseServerString(s: String): ElectrumxServer? {
@@ -77,17 +77,15 @@ data class NamecoinSettings(
if (host.isEmpty() || port <= 0 || port > 65535) return null if (host.isEmpty() || port <= 0 || port > 65535) return null
val useSsl = parts.getOrNull(2)?.trim()?.lowercase() != "tcp" val useSsl = parts.getOrNull(2)?.trim()?.lowercase() != "tcp"
val isOnion = host.endsWith(".onion") val isOnion = host.endsWith(".onion")
// All custom servers set trustAllCerts=true, which (despite the // All custom servers use the pinned trust store. ElectrumX
// legacy name) means "use the pinned trust store" rather than // servers almost universally use self-signed certs, so we
// "trust all certificates". ElectrumX servers almost universally // route them through our pinned SSLSocketFactory (hardcoded
// use self-signed certs, so we route them through our pinned // defaults + TOFU-pinned certs + system CAs).
// SSLSocketFactory (hardcoded defaults + TOFU-pinned certs).
// TODO: rename trustAllCerts → usePinnedTrustStore for clarity.
return ElectrumxServer( return ElectrumxServer(
host = host, host = host,
port = port, port = port,
useSsl = useSsl, useSsl = useSsl,
trustAllCerts = true, usePinnedTrustStore = true,
) )
} }
@@ -56,7 +56,7 @@ class NamecoinSettingsTest {
assertEquals("abc123def.onion", s!!.host) assertEquals("abc123def.onion", s!!.host)
assertEquals(50001, s.port) assertEquals(50001, s.port)
assertFalse(s.useSsl) assertFalse(s.useSsl)
assertTrue(s.trustAllCerts) assertTrue(s.usePinnedTrustStore)
} }
@Test @Test
@@ -131,7 +131,7 @@ class NamecoinSettingsTest {
assertTrue(servers[0].useSsl) assertTrue(servers[0].useSsl)
assertEquals("server2.onion", servers[1].host) assertEquals("server2.onion", servers[1].host)
assertFalse(servers[1].useSsl) assertFalse(servers[1].useSsl)
assertTrue(servers[1].trustAllCerts) assertTrue(servers[1].usePinnedTrustStore)
} }
@Test @Test
@@ -44,8 +44,14 @@ data class ElectrumxServer(
val host: String, val host: String,
val port: Int, val port: Int,
val useSsl: Boolean = true, val useSsl: Boolean = true,
/** If true, accept any certificate (self-signed, expired, etc.) */ /**
val trustAllCerts: Boolean = false, * If true, use the pinned trust store (hardcoded + TOFU-pinned certs
* plus system CAs) instead of the default system-only trust store.
*
* Required for ElectrumX servers that use self-signed certificates,
* which is the norm for the Namecoin ElectrumX ecosystem.
*/
val usePinnedTrustStore: Boolean = false,
) )
/** /**
@@ -90,9 +96,9 @@ data class ServerTestResult(
/** Well-known public Namecoin ElectrumX servers (clearnet). */ /** Well-known public Namecoin ElectrumX servers (clearnet). */
val DEFAULT_ELECTRUMX_SERVERS = val DEFAULT_ELECTRUMX_SERVERS =
listOf( listOf(
ElectrumxServer("electrumx.testls.space", 50002, useSsl = true, trustAllCerts = true), ElectrumxServer("electrumx.testls.space", 50002, useSsl = true, usePinnedTrustStore = true),
ElectrumxServer("nmc2.bitcoins.sk", 57002, useSsl = true, trustAllCerts = true), ElectrumxServer("nmc2.bitcoins.sk", 57002, useSsl = true, usePinnedTrustStore = true),
ElectrumxServer("46.229.238.187", 57002, useSsl = true, trustAllCerts = true), ElectrumxServer("46.229.238.187", 57002, useSsl = true, usePinnedTrustStore = true),
) )
/** Tor-preferred server list: onion primary, clearnet fallback. */ /** Tor-preferred server list: onion primary, clearnet fallback. */
@@ -102,8 +108,8 @@ val TOR_ELECTRUMX_SERVERS =
"i665jpwsq46zlsdbnj4axgzd3s56uzey5uhotsnxzsknzbn36jaddsid.onion", "i665jpwsq46zlsdbnj4axgzd3s56uzey5uhotsnxzsknzbn36jaddsid.onion",
50002, 50002,
useSsl = true, useSsl = true,
trustAllCerts = true, usePinnedTrustStore = true,
), ),
ElectrumxServer("electrumx.testls.space", 50002, useSsl = true, trustAllCerts = true), ElectrumxServer("electrumx.testls.space", 50002, useSsl = true, usePinnedTrustStore = true),
ElectrumxServer("nmc2.bitcoins.sk", 57002, useSsl = true, trustAllCerts = true), ElectrumxServer("nmc2.bitcoins.sk", 57002, useSsl = true, usePinnedTrustStore = true),
) )
@@ -601,14 +601,10 @@ class ElectrumXClient(
if (!server.useSsl) return baseSocket if (!server.useSsl) return baseSocket
// Upgrade to TLS over the already-connected (possibly proxied) socket. // Upgrade to TLS over the already-connected (possibly proxied) socket.
// When the server uses a self-signed certificate (trustAllCerts flag), // When usePinnedTrustStore is set, we use a pinned trust store that
// we use a pinned trust store that contains the known ElectrumX server // contains the known ElectrumX server certs plus system CAs. This is
// certs. This is required because Samsung One UI 7 (Android 16) silently // required because Samsung One UI 7 (Android 16) and GrapheneOS
// rejects connections that use a no-op "trust-all" X509TrustManager. // reject connections that use a no-op "trust-all" X509TrustManager.
//
// Exception: .onion addresses bypass cert pinning entirely — the Tor
// hidden service protocol already provides end-to-end authentication
// via the onion address, making TLS cert verification redundant.
val sslFactory = val sslFactory =
if (server.host.endsWith(".onion")) { if (server.host.endsWith(".onion")) {
// .onion addresses: prefer the pinned factory (the .onion server's // .onion addresses: prefer the pinned factory (the .onion server's
@@ -618,7 +614,7 @@ class ElectrumXClient(
// blanket trust-all that hardened TLS stacks (GrapheneOS, Samsung // blanket trust-all that hardened TLS stacks (GrapheneOS, Samsung
// Knox) may reject at the Conscrypt/BoringSSL layer. // Knox) may reject at the Conscrypt/BoringSSL layer.
cachedPinnedSslFactory() cachedPinnedSslFactory()
} else if (server.trustAllCerts) { } else if (server.usePinnedTrustStore) {
cachedPinnedSslFactory() cachedPinnedSslFactory()
} else { } else {
SSLSocketFactory.getDefault() as SSLSocketFactory SSLSocketFactory.getDefault() as SSLSocketFactory