mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 02:25:20 +00:00
Fix crash: Add thread safety to EncryptionService
The crash was caused by concurrent access to CryptoKit signing operations from multiple threads. Added thread-safe access using a concurrent queue with barriers for write operations and sync reads. This fixes the EXC_BAD_ACCESS crash seen in TestFlight build.
This commit is contained in:
@@ -482,6 +482,7 @@
|
|||||||
DEVELOPMENT_TEAM = L3N5LHJD5Y;
|
DEVELOPMENT_TEAM = L3N5LHJD5Y;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = bitchat/Info.plist;
|
INFOPLIST_FILE = bitchat/Info.plist;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -491,9 +492,12 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat;
|
PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat;
|
||||||
PRODUCT_NAME = bitchat;
|
PRODUCT_NAME = bitchat;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = NO;
|
||||||
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
|
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -528,6 +532,7 @@
|
|||||||
DEVELOPMENT_TEAM = L3N5LHJD5Y;
|
DEVELOPMENT_TEAM = L3N5LHJD5Y;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = bitchat/Info.plist;
|
INFOPLIST_FILE = bitchat/Info.plist;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -537,9 +542,12 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat;
|
PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat;
|
||||||
PRODUCT_NAME = bitchat;
|
PRODUCT_NAME = bitchat;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = NO;
|
||||||
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
|
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ class EncryptionService {
|
|||||||
private let identityKey: Curve25519.Signing.PrivateKey
|
private let identityKey: Curve25519.Signing.PrivateKey
|
||||||
public let identityPublicKey: Curve25519.Signing.PublicKey
|
public let identityPublicKey: Curve25519.Signing.PublicKey
|
||||||
|
|
||||||
|
// Thread safety
|
||||||
|
private let cryptoQueue = DispatchQueue(label: "chat.bitchat.crypto", attributes: .concurrent)
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// Generate ephemeral key pairs for this session
|
// Generate ephemeral key pairs for this session
|
||||||
self.privateKey = Curve25519.KeyAgreement.PrivateKey()
|
self.privateKey = Curve25519.KeyAgreement.PrivateKey()
|
||||||
@@ -59,46 +62,50 @@ class EncryptionService {
|
|||||||
|
|
||||||
// Add peer's combined public keys
|
// Add peer's combined public keys
|
||||||
func addPeerPublicKey(_ peerID: String, publicKeyData: Data) throws {
|
func addPeerPublicKey(_ peerID: String, publicKeyData: Data) throws {
|
||||||
// Convert to array for safe access
|
try cryptoQueue.sync(flags: .barrier) {
|
||||||
let keyBytes = [UInt8](publicKeyData)
|
// Convert to array for safe access
|
||||||
|
let keyBytes = [UInt8](publicKeyData)
|
||||||
|
|
||||||
guard keyBytes.count == 96 else {
|
guard keyBytes.count == 96 else {
|
||||||
// print("[CRYPTO] Invalid public key data size: \(keyBytes.count), expected 96")
|
// print("[CRYPTO] Invalid public key data size: \(keyBytes.count), expected 96")
|
||||||
throw EncryptionError.invalidPublicKey
|
throw EncryptionError.invalidPublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract all three keys: 32 for key agreement + 32 for signing + 32 for identity
|
// Extract all three keys: 32 for key agreement + 32 for signing + 32 for identity
|
||||||
let keyAgreementData = Data(keyBytes[0..<32])
|
let keyAgreementData = Data(keyBytes[0..<32])
|
||||||
let signingKeyData = Data(keyBytes[32..<64])
|
let signingKeyData = Data(keyBytes[32..<64])
|
||||||
let identityKeyData = Data(keyBytes[64..<96])
|
let identityKeyData = Data(keyBytes[64..<96])
|
||||||
|
|
||||||
let publicKey = try Curve25519.KeyAgreement.PublicKey(rawRepresentation: keyAgreementData)
|
let publicKey = try Curve25519.KeyAgreement.PublicKey(rawRepresentation: keyAgreementData)
|
||||||
peerPublicKeys[peerID] = publicKey
|
peerPublicKeys[peerID] = publicKey
|
||||||
|
|
||||||
let signingKey = try Curve25519.Signing.PublicKey(rawRepresentation: signingKeyData)
|
let signingKey = try Curve25519.Signing.PublicKey(rawRepresentation: signingKeyData)
|
||||||
peerSigningKeys[peerID] = signingKey
|
peerSigningKeys[peerID] = signingKey
|
||||||
|
|
||||||
let identityKey = try Curve25519.Signing.PublicKey(rawRepresentation: identityKeyData)
|
let identityKey = try Curve25519.Signing.PublicKey(rawRepresentation: identityKeyData)
|
||||||
peerIdentityKeys[peerID] = identityKey
|
peerIdentityKeys[peerID] = identityKey
|
||||||
|
|
||||||
// Stored all three keys for peer
|
// Stored all three keys for peer
|
||||||
|
|
||||||
// Generate shared secret for encryption
|
// Generate shared secret for encryption
|
||||||
if let publicKey = peerPublicKeys[peerID] {
|
if let publicKey = peerPublicKeys[peerID] {
|
||||||
let sharedSecret = try privateKey.sharedSecretFromKeyAgreement(with: publicKey)
|
let sharedSecret = try privateKey.sharedSecretFromKeyAgreement(with: publicKey)
|
||||||
let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(
|
let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(
|
||||||
using: SHA256.self,
|
using: SHA256.self,
|
||||||
salt: "bitchat-v1".data(using: .utf8)!,
|
salt: "bitchat-v1".data(using: .utf8)!,
|
||||||
sharedInfo: Data(),
|
sharedInfo: Data(),
|
||||||
outputByteCount: 32
|
outputByteCount: 32
|
||||||
)
|
)
|
||||||
sharedSecrets[peerID] = symmetricKey
|
sharedSecrets[peerID] = symmetricKey
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get peer's persistent identity key for favorites
|
// Get peer's persistent identity key for favorites
|
||||||
func getPeerIdentityKey(_ peerID: String) -> Data? {
|
func getPeerIdentityKey(_ peerID: String) -> Data? {
|
||||||
return peerIdentityKeys[peerID]?.rawRepresentation
|
return cryptoQueue.sync {
|
||||||
|
return peerIdentityKeys[peerID]?.rawRepresentation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear persistent identity (for panic mode)
|
// Clear persistent identity (for panic mode)
|
||||||
@@ -108,8 +115,11 @@ class EncryptionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func encrypt(_ data: Data, for peerID: String) throws -> Data {
|
func encrypt(_ data: Data, for peerID: String) throws -> Data {
|
||||||
guard let symmetricKey = sharedSecrets[peerID] else {
|
let symmetricKey = try cryptoQueue.sync {
|
||||||
throw EncryptionError.noSharedSecret
|
guard let key = sharedSecrets[peerID] else {
|
||||||
|
throw EncryptionError.noSharedSecret
|
||||||
|
}
|
||||||
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
let sealedBox = try AES.GCM.seal(data, using: symmetricKey)
|
let sealedBox = try AES.GCM.seal(data, using: symmetricKey)
|
||||||
@@ -117,8 +127,11 @@ class EncryptionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func decrypt(_ data: Data, from peerID: String) throws -> Data {
|
func decrypt(_ data: Data, from peerID: String) throws -> Data {
|
||||||
guard let symmetricKey = sharedSecrets[peerID] else {
|
let symmetricKey = try cryptoQueue.sync {
|
||||||
throw EncryptionError.noSharedSecret
|
guard let key = sharedSecrets[peerID] else {
|
||||||
|
throw EncryptionError.noSharedSecret
|
||||||
|
}
|
||||||
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
let sealedBox = try AES.GCM.SealedBox(combined: data)
|
let sealedBox = try AES.GCM.SealedBox(combined: data)
|
||||||
@@ -126,12 +139,17 @@ class EncryptionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sign(_ data: Data) throws -> Data {
|
func sign(_ data: Data) throws -> Data {
|
||||||
return try signingPrivateKey.signature(for: data)
|
// Create a local copy of the key to avoid concurrent access
|
||||||
|
let key = signingPrivateKey
|
||||||
|
return try key.signature(for: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func verify(_ signature: Data, for data: Data, from peerID: String) throws -> Bool {
|
func verify(_ signature: Data, for data: Data, from peerID: String) throws -> Bool {
|
||||||
guard let verifyingKey = peerSigningKeys[peerID] else {
|
let verifyingKey = try cryptoQueue.sync {
|
||||||
throw EncryptionError.noSharedSecret
|
guard let key = peerSigningKeys[peerID] else {
|
||||||
|
throw EncryptionError.noSharedSecret
|
||||||
|
}
|
||||||
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
return verifyingKey.isValidSignature(signature, for: data)
|
return verifyingKey.isValidSignature(signature, for: data)
|
||||||
|
|||||||
Reference in New Issue
Block a user