Merge pull request #3 from newtonick/insecure-ssl-support

Feature: Add Insecure SSL support for electrum server
This commit is contained in:
Nick Klockenga 2026-03-30 22:59:52 -04:00 committed by GitHub
commit 5165a3609d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 112 additions and 21 deletions

View File

@ -7,9 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
3C08EDBF2F5E3A8000C2268E /* BitcoinDevKit in Frameworks */ = {isa = PBXBuildFile; productRef = AA00000200000000000000D0 /* BitcoinDevKit */; };
3C08EDC02F5E3A8000C2268E /* URKit in Frameworks */ = {isa = PBXBuildFile; productRef = AA00000500000000000000D0 /* URKit */; };
3C08EDC12F5E3A8000C2268E /* URUI in Frameworks */ = {isa = PBXBuildFile; productRef = AA00000800000000000000D0 /* URUI */; };
3C1E1C462F7B0D99002FDAE2 /* BitcoinDevKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3C1E1C452F7B0D99002FDAE2 /* BitcoinDevKit */; };
3C1E1FA52F7B5F63002FDAE2 /* BitcoinDevKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3C1E1FA42F7B5F63002FDAE2 /* BitcoinDevKit */; };
AA0000B100000000000000D0 /* Bbqr in Frameworks */ = {isa = PBXBuildFile; productRef = AA0000B000000000000000D0 /* Bbqr */; };
/* End PBXBuildFile section */
@ -59,9 +60,10 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3C1E1C462F7B0D99002FDAE2 /* BitcoinDevKit in Frameworks */,
3C08EDC12F5E3A8000C2268E /* URUI in Frameworks */,
3C08EDC02F5E3A8000C2268E /* URKit in Frameworks */,
3C08EDBF2F5E3A8000C2268E /* BitcoinDevKit in Frameworks */,
3C1E1FA52F7B5F63002FDAE2 /* BitcoinDevKit in Frameworks */,
AA0000B100000000000000D0 /* Bbqr in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -123,10 +125,11 @@
);
name = hellbender;
packageProductDependencies = (
AA00000200000000000000D0 /* BitcoinDevKit */,
AA00000500000000000000D0 /* URKit */,
AA00000800000000000000D0 /* URUI */,
AA0000B000000000000000D0 /* Bbqr */,
3C1E1C452F7B0D99002FDAE2 /* BitcoinDevKit */,
3C1E1FA42F7B5F63002FDAE2 /* BitcoinDevKit */,
);
productName = hellbender;
productReference = 3C9ACE242F5DED94009B00D0 /* hellbender.app */;
@ -211,10 +214,10 @@
mainGroup = 3C9ACE1B2F5DED94009B00D0;
minimizedProjectReferenceProxies = 1;
packageReferences = (
AA00000100000000000000D0 /* XCRemoteSwiftPackageReference "bdk-swift" */,
AA00000400000000000000D0 /* XCRemoteSwiftPackageReference "URKit" */,
AA00000700000000000000D0 /* XCRemoteSwiftPackageReference "URUI" */,
AA0000A000000000000000D0 /* XCRemoteSwiftPackageReference "bbqr-swift" */,
3C1E1FA32F7B5F63002FDAE2 /* XCRemoteSwiftPackageReference "bdk-swift" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 3C9ACE252F5DED94009B00D0 /* Products */;
@ -589,12 +592,12 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
AA00000100000000000000D0 /* XCRemoteSwiftPackageReference "bdk-swift" */ = {
3C1E1FA32F7B5F63002FDAE2 /* XCRemoteSwiftPackageReference "bdk-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/bitcoindevkit/bdk-swift";
repositoryURL = "https://github.com/newtonick/bdk-swift";
requirement = {
kind = exactVersion;
version = 2.3.1;
version = "2.3.1-ssl-patch";
};
};
AA00000400000000000000D0 /* XCRemoteSwiftPackageReference "URKit" */ = {
@ -624,9 +627,13 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
AA00000200000000000000D0 /* BitcoinDevKit */ = {
3C1E1C452F7B0D99002FDAE2 /* BitcoinDevKit */ = {
isa = XCSwiftPackageProductDependency;
package = AA00000100000000000000D0 /* XCRemoteSwiftPackageReference "bdk-swift" */;
productName = BitcoinDevKit;
};
3C1E1FA42F7B5F63002FDAE2 /* BitcoinDevKit */ = {
isa = XCSwiftPackageProductDependency;
package = 3C1E1FA32F7B5F63002FDAE2 /* XCRemoteSwiftPackageReference "bdk-swift" */;
productName = BitcoinDevKit;
};
AA00000500000000000000D0 /* URKit */ = {

View File

@ -1,5 +1,5 @@
{
"originHash" : "561131c8e974cf1aaf4e7fb48eebaf74a94dabe0b90801de672b9398a0361a37",
"originHash" : "11f3c5d73e6615e055e5b9f3671e6180f277a34f298c3f7c6935dcc8dd281089",
"pins" : [
{
"identity" : "bbqr-swift",
@ -40,10 +40,10 @@
{
"identity" : "bdk-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/bitcoindevkit/bdk-swift",
"location" : "https://github.com/newtonick/bdk-swift",
"state" : {
"revision" : "c38d7caa06ec26289cb40b1ea42531aea631c47e",
"version" : "2.3.1"
"revision" : "4660bc83ea6088906edb090652d261e8ed4c09e3",
"version" : "2.3.1-ssl-patch"
}
},
{

View File

@ -17,6 +17,7 @@ final class WalletProfile {
var electrumPort: Int
var electrumSSL: Int // 0 = network default, 1 = TCP, 2 = SSL
var blockExplorerHost: String // empty = mempool.space
var electrumAllowInsecureSSL: Bool = false
var privacyMode: Bool = false
@Relationship(deleteRule: .cascade, inverse: \CosignerInfo.wallet)
@ -35,6 +36,7 @@ final class WalletProfile {
electrumHost: String = "",
electrumPort: Int = 0,
electrumSSL: Int = 0,
electrumAllowInsecureSSL: Bool = false,
blockExplorerHost: String = "",
privacyMode: Bool = false
) {
@ -51,6 +53,7 @@ final class WalletProfile {
self.electrumHost = electrumHost
self.electrumPort = electrumPort
self.electrumSSL = electrumSSL
self.electrumAllowInsecureSSL = electrumAllowInsecureSSL
self.blockExplorerHost = blockExplorerHost
self.privacyMode = privacyMode
cosigners = []
@ -69,7 +72,7 @@ final class WalletProfile {
case 2: true // SSL
default: net.usesSSL // 0 = network default
}
return ElectrumConfig(host: host, port: port, useSSL: ssl)
return ElectrumConfig(host: host, port: port, useSSL: ssl, allowInsecureSSL: electrumAllowInsecureSSL)
}
var multisigDescription: String {

View File

@ -267,7 +267,8 @@ final class BitcoinService {
addToLog("Connecting to Electrum: \(config.url)")
do {
let url = config.url
electrumClient = try await Task.detached { try ElectrumClient(url: url) }.value
let validateDomain = !config.allowInsecureSSL
electrumClient = try await Task.detached { try ElectrumClient(url: url, validateDomain: validateDomain) }.value
electrumConnectionError = nil
addToLog("Electrum client initialized")
} catch {
@ -304,7 +305,8 @@ final class BitcoinService {
let config = profile.electrumConfig
addToLog("Re-initializing Electrum client: \(config.url)")
let reconnectURL = config.url
electrumClient = try await Task.detached { try ElectrumClient(url: reconnectURL) }.value
let validateDomain = !config.allowInsecureSSL
electrumClient = try await Task.detached { try ElectrumClient(url: reconnectURL, validateDomain: validateDomain) }.value
electrumConnectionError = nil
}
@ -459,13 +461,14 @@ final class BitcoinService {
@discardableResult
func testElectrumConnection(config: ElectrumConfig) async throws -> UInt32 {
// Pre-check SSL certificate before handing off to BDK
if config.useSSL {
if config.useSSL, !config.allowInsecureSSL {
try await Self.validateTLSCertificate(host: config.host, port: config.port)
}
let url = config.url
let validateDomain = !config.allowInsecureSSL
let header = try await Task.detached {
let client = try ElectrumClient(url: url)
let client = try ElectrumClient(url: url, validateDomain: validateDomain)
return try client.blockHeadersSubscribe()
}.value
return UInt32(header.height)

View File

@ -4,21 +4,24 @@ struct ElectrumConfig: Equatable {
var host: String
var port: UInt16
var useSSL: Bool
var allowInsecureSSL: Bool
var url: String {
let proto = useSSL ? "ssl" : "tcp"
return "\(proto)://\(host):\(port)"
}
init(host: String, port: UInt16, useSSL: Bool) {
init(host: String, port: UInt16, useSSL: Bool, allowInsecureSSL: Bool = false) {
self.host = host
self.port = port
self.useSSL = useSSL
self.allowInsecureSSL = allowInsecureSSL
}
init(network: BitcoinNetwork) {
host = network.defaultElectrumHost ?? ""
port = network.defaultElectrumPort
useSSL = network.usesSSL
allowInsecureSSL = false
}
}

View File

@ -50,6 +50,7 @@ final class SetupWizardViewModel {
var electrumHost: String = ""
var electrumPort: String = ""
var electrumSSL: Int = 0 // 0 = network default, 1 = TCP, 2 = SSL
var electrumAllowInsecureSSL: Bool = false
/// Returns an error message if the descriptor contains keys that don't match the selected network, nil otherwise.
var descriptorNetworkMismatchError: String? {
@ -449,6 +450,7 @@ final class SetupWizardViewModel {
electrumHost: electrumHost.trimmingCharacters(in: .whitespaces),
electrumPort: Int(electrumPort) ?? 0,
electrumSSL: electrumSSL,
electrumAllowInsecureSSL: electrumAllowInsecureSSL,
blockExplorerHost: blockExplorerHost.trimmingCharacters(in: .whitespaces)
)

View File

@ -23,6 +23,7 @@ struct WalletInfoView: View {
@State private var connectionTestResult: String?
@State private var blockExplorerText: String = ""
@State private var initialElectrumConfig: ElectrumConfig?
@State private var showInsecureSSLAlert = false
@State private var showDescriptorQR = false
@State private var showDeleteConfirmation = false
@State private var showDescriptorPDF = false
@ -200,7 +201,12 @@ struct WalletInfoView: View {
default: wallet.bitcoinNetwork.usesSSL ? 2 : 1
}
},
set: { wallet.electrumSSL = $0 }
set: {
wallet.electrumSSL = $0
if $0 != 2 {
wallet.electrumAllowInsecureSSL = false
}
}
)) {
Text("TCP").tag(1)
Text("SSL").tag(2)
@ -208,6 +214,24 @@ struct WalletInfoView: View {
.pickerStyle(.segmented)
}
if wallet.electrumSSL == 2 || (wallet.electrumSSL == 0 && wallet.bitcoinNetwork.usesSSL) {
Toggle(isOn: Binding(
get: { wallet.electrumAllowInsecureSSL },
set: { newValue in
if newValue {
showInsecureSSLAlert = true
} else {
wallet.electrumAllowInsecureSSL = false
}
}
)) {
Text("Allow insecure SSL")
.font(.hbBody(13))
.foregroundStyle(Color.hbTextPrimary)
}
.tint(Color.hbBitcoinOrange)
}
if let result = connectionTestResult {
Text(result)
.font(.hbBody(13))
@ -426,6 +450,14 @@ struct WalletInfoView: View {
}
}
}
.alert("Allow Insecure SSL?", isPresented: $showInsecureSSLAlert) {
Button("Cancel", role: .cancel) {}
Button("Allow", role: .destructive) {
wallet.electrumAllowInsecureSSL = true
}
} message: {
Text("This removes the requirement to verify that the server is who it claims to be. The connection will still be encrypted, but self-signed, expired, or invalid certificates will be accepted.")
}
.sheet(isPresented: $showEditCosigners) {
EditCosignersView(wallet: wallet)
}
@ -460,6 +492,7 @@ struct WalletInfoView: View {
wallet.electrumHost = ""
wallet.electrumPort = 0
wallet.electrumSSL = 0
wallet.electrumAllowInsecureSSL = false
electrumHostText = ""
electrumPortText = ""
connectionTestResult = nil

View File

@ -2,6 +2,15 @@ import SwiftUI
struct ElectrumServerSetupSection: View {
@Bindable var viewModel: SetupWizardViewModel
@State private var showInsecureSSLAlert = false
private var isSSLSelected: Bool {
switch viewModel.electrumSSL {
case 1: false
case 2: true
default: viewModel.network.usesSSL
}
}
var body: some View {
VStack(spacing: 12) {
@ -50,7 +59,12 @@ struct ElectrumServerSetupSection: View {
default: viewModel.network.usesSSL ? 2 : 1
}
},
set: { viewModel.electrumSSL = $0 }
set: {
viewModel.electrumSSL = $0
if $0 != 2 {
viewModel.electrumAllowInsecureSSL = false
}
}
)) {
Text("TCP").tag(1)
Text("SSL").tag(2)
@ -59,6 +73,24 @@ struct ElectrumServerSetupSection: View {
}
}
if isSSLSelected {
Toggle(isOn: Binding(
get: { viewModel.electrumAllowInsecureSSL },
set: { newValue in
if newValue {
showInsecureSSLAlert = true
} else {
viewModel.electrumAllowInsecureSSL = false
}
}
)) {
Text("Allow insecure SSL")
.font(.hbBody(13))
.foregroundStyle(Color.hbTextPrimary)
}
.tint(Color.hbBitcoinOrange)
}
if viewModel.network.defaultElectrumHost != nil {
Text("Leave blank to use defaults for \(viewModel.network.displayName)")
.font(.hbBody(11))
@ -70,6 +102,14 @@ struct ElectrumServerSetupSection: View {
}
}
.hbCard()
.alert("Allow Insecure SSL?", isPresented: $showInsecureSSLAlert) {
Button("Cancel", role: .cancel) {}
Button("Allow", role: .destructive) {
viewModel.electrumAllowInsecureSSL = true
}
} message: {
Text("This removes the requirement to verify that the server is who it claims to be. The connection will still be encrypted, but self-signed, expired, or invalid certificates will be accepted.")
}
}
}