mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-24 23:25:19 +00:00
Extract Noise into a dedicated module
This commit is contained in:
@@ -21,6 +21,8 @@ jobs:
|
||||
path: localPackages/BitLogger
|
||||
- name: BitFoundation
|
||||
path: localPackages/BitFoundation
|
||||
- name: Noise
|
||||
path: localPackages/Noise
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
||||
+3
-2
@@ -17,6 +17,7 @@ let package = Package(
|
||||
],
|
||||
dependencies:[
|
||||
.package(path: "localPackages/Arti"),
|
||||
.package(path: "localPackages/Noise"),
|
||||
.package(path: "localPackages/BitFoundation"),
|
||||
.package(path: "localPackages/BitLogger"),
|
||||
.package(url: "https://github.com/21-DOT-DEV/swift-secp256k1", exact: "0.21.1")
|
||||
@@ -28,6 +29,7 @@ let package = Package(
|
||||
.product(name: "P256K", package: "swift-secp256k1"),
|
||||
.product(name: "BitFoundation", package: "BitFoundation"),
|
||||
.product(name: "BitLogger", package: "BitLogger"),
|
||||
.product(name: "Noise", package: "Noise"),
|
||||
.product(name: "Tor", package: "Arti")
|
||||
],
|
||||
path: "bitchat",
|
||||
@@ -56,8 +58,7 @@ let package = Package(
|
||||
"README.md"
|
||||
],
|
||||
resources: [
|
||||
.process("Localization"),
|
||||
.process("Noise")
|
||||
.process("Localization")
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
Generated
+20
@@ -10,6 +10,8 @@
|
||||
17901751FD8010AFC8E750F2 /* bitchatShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 61F92EBA29C47C0FCC482F1F /* bitchatShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
3EE336D150427F736F32B56C /* P256K in Frameworks */ = {isa = PBXBuildFile; productRef = B1D9136AA0083366353BFA2F /* P256K */; };
|
||||
885BBED78092484A5B069461 /* P256K in Frameworks */ = {isa = PBXBuildFile; productRef = 4EB6BA1B8464F1EA38F4E286 /* P256K */; };
|
||||
A63163B62F80CB2500B8B128 /* Noise in Frameworks */ = {isa = PBXBuildFile; productRef = A63163B52F80CB2500B8B128 /* Noise */; };
|
||||
A63163B82F80CB2D00B8B128 /* Noise in Frameworks */ = {isa = PBXBuildFile; productRef = A63163B72F80CB2D00B8B128 /* Noise */; };
|
||||
A6BCF9482F80953E001CF9B9 /* BitFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = A6BCF9472F80953E001CF9B9 /* BitFoundation */; };
|
||||
A6BCF94A2F809550001CF9B9 /* BitFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = A6BCF9492F809550001CF9B9 /* BitFoundation */; };
|
||||
A6E3E5702E77036A0032EA8A /* BitLogger in Frameworks */ = {isa = PBXBuildFile; productRef = A6E3E56F2E77036A0032EA8A /* BitLogger */; };
|
||||
@@ -156,6 +158,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
files = (
|
||||
A6E3E5722E7703760032EA8A /* BitLogger in Frameworks */,
|
||||
A63163B82F80CB2D00B8B128 /* Noise in Frameworks */,
|
||||
3EE336D150427F736F32B56C /* P256K in Frameworks */,
|
||||
A6E3EA812E7706A80032EA8A /* Tor in Frameworks */,
|
||||
A6BCF94A2F809550001CF9B9 /* BitFoundation in Frameworks */,
|
||||
@@ -165,6 +168,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
files = (
|
||||
A6E3E5702E77036A0032EA8A /* BitLogger in Frameworks */,
|
||||
A63163B62F80CB2500B8B128 /* Noise in Frameworks */,
|
||||
885BBED78092484A5B069461 /* P256K in Frameworks */,
|
||||
A6E3EA7F2E7706720032EA8A /* Tor in Frameworks */,
|
||||
A6BCF9482F80953E001CF9B9 /* BitFoundation in Frameworks */,
|
||||
@@ -228,6 +232,7 @@
|
||||
A6E3E5712E7703760032EA8A /* BitLogger */,
|
||||
A6E3EA802E7706A80032EA8A /* Tor */,
|
||||
A6BCF9492F809550001CF9B9 /* BitFoundation */,
|
||||
A63163B72F80CB2D00B8B128 /* Noise */,
|
||||
);
|
||||
productName = bitchat_macOS;
|
||||
productReference = 8F3A7C058C2C8E1A06C8CF8B /* bitchat.app */;
|
||||
@@ -309,6 +314,7 @@
|
||||
A6E3E56F2E77036A0032EA8A /* BitLogger */,
|
||||
A6E3EA7E2E7706720032EA8A /* Tor */,
|
||||
A6BCF9472F80953E001CF9B9 /* BitFoundation */,
|
||||
A63163B52F80CB2500B8B128 /* Noise */,
|
||||
);
|
||||
productName = bitchat_iOS;
|
||||
productReference = 96D0D41CA19EE5A772AA8434 /* bitchat.app */;
|
||||
@@ -351,6 +357,7 @@
|
||||
A6E3E56E2E77036A0032EA8A /* XCLocalSwiftPackageReference "localPackages/BitLogger" */,
|
||||
A6E3EA7D2E7706720032EA8A /* XCLocalSwiftPackageReference "localPackages/Arti" */,
|
||||
A6BCF9462F80953E001CF9B9 /* XCLocalSwiftPackageReference "localPackages/BitFoundation" */,
|
||||
A63163B42F80CB2500B8B128 /* XCLocalSwiftPackageReference "localPackages/Noise" */,
|
||||
);
|
||||
preferredProjectObjectVersion = 90;
|
||||
projectDirPath = "";
|
||||
@@ -916,6 +923,10 @@
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCLocalSwiftPackageReference section */
|
||||
A63163B42F80CB2500B8B128 /* XCLocalSwiftPackageReference "localPackages/Noise" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = localPackages/Noise;
|
||||
};
|
||||
A6BCF9462F80953E001CF9B9 /* XCLocalSwiftPackageReference "localPackages/BitFoundation" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = localPackages/BitFoundation;
|
||||
@@ -947,6 +958,15 @@
|
||||
package = B8C407587481BBB190741C93 /* XCRemoteSwiftPackageReference "swift-secp256k1" */;
|
||||
productName = P256K;
|
||||
};
|
||||
A63163B52F80CB2500B8B128 /* Noise */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Noise;
|
||||
};
|
||||
A63163B72F80CB2D00B8B128 /* Noise */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = A63163B42F80CB2500B8B128 /* XCLocalSwiftPackageReference "localPackages/Noise" */;
|
||||
productName = Noise;
|
||||
};
|
||||
A6BCF9472F80953E001CF9B9 /* BitFoundation */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = BitFoundation;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import BitLogger
|
||||
import protocol Noise.SecureMemoryCleaner
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
@@ -51,7 +52,7 @@ enum KeychainSaveResult {
|
||||
}
|
||||
}
|
||||
|
||||
protocol KeychainManagerProtocol {
|
||||
protocol KeychainManagerProtocol: SecureMemoryCleaner {
|
||||
func saveIdentityKey(_ keyData: Data, forKey key: String) -> Bool
|
||||
func getIdentityKey(forKey key: String) -> Data?
|
||||
func deleteIdentityKey(forKey key: String) -> Bool
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
|
||||
import BitLogger
|
||||
import BitFoundation
|
||||
import Noise
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import Testing
|
||||
import CryptoKit
|
||||
import struct Foundation.UUID
|
||||
import BitFoundation
|
||||
import Noise
|
||||
@testable import bitchat
|
||||
|
||||
struct PrivateChatE2ETests {
|
||||
|
||||
@@ -10,6 +10,7 @@ import Foundation
|
||||
import CryptoKit
|
||||
import Testing
|
||||
import BitFoundation
|
||||
import Noise
|
||||
@testable import bitchat
|
||||
|
||||
struct IntegrationTests {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
import BitFoundation
|
||||
import Noise
|
||||
@testable import bitchat
|
||||
|
||||
final class TestNetworkHelper {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// swift-tools-version: 5.9
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Noise",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
.macOS(.v13)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "Noise",
|
||||
targets: ["Noise"]
|
||||
),
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../BitLogger"),
|
||||
.package(path: "../BitFoundation"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Noise",
|
||||
dependencies: [
|
||||
.product(name: "BitLogger", package: "BitLogger"),
|
||||
.product(name: "BitFoundation", package: "BitFoundation"),
|
||||
],
|
||||
path: "Sources"
|
||||
),
|
||||
.testTarget(
|
||||
name: "NoiseTests",
|
||||
dependencies: ["Noise"],
|
||||
resources: [
|
||||
.process("NoiseTestVectors.json")
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
+4
-3
@@ -78,6 +78,7 @@
|
||||
///
|
||||
|
||||
import BitLogger
|
||||
import BitFoundation
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
@@ -532,7 +533,7 @@ final class NoiseSymmetricState {
|
||||
final class NoiseHandshakeState {
|
||||
private let role: NoiseRole
|
||||
private let pattern: NoisePattern
|
||||
private let keychain: KeychainManagerProtocol
|
||||
private let keychain: SecureMemoryCleaner
|
||||
private var symmetricState: NoiseSymmetricState
|
||||
|
||||
// Keys
|
||||
@@ -555,7 +556,7 @@ final class NoiseHandshakeState {
|
||||
init(
|
||||
role: NoiseRole,
|
||||
pattern: NoisePattern,
|
||||
keychain: KeychainManagerProtocol,
|
||||
keychain: SecureMemoryCleaner,
|
||||
localStaticKey: Curve25519.KeyAgreement.PrivateKey? = nil,
|
||||
remoteStaticKey: Curve25519.KeyAgreement.PublicKey? = nil,
|
||||
prologue: Data = Data(),
|
||||
@@ -923,7 +924,7 @@ extension NoisePattern {
|
||||
|
||||
// MARK: - Errors
|
||||
|
||||
enum NoiseError: Error {
|
||||
public enum NoiseError: Error {
|
||||
case uninitializedCipher
|
||||
case invalidCiphertext
|
||||
case handshakeComplete
|
||||
+7
-5
@@ -10,7 +10,7 @@ import BitLogger
|
||||
import BitFoundation
|
||||
import Foundation
|
||||
|
||||
final class NoiseRateLimiter {
|
||||
public final class NoiseRateLimiter {
|
||||
private var handshakeTimestamps: [PeerID: [Date]] = [:]
|
||||
private var messageTimestamps: [PeerID: [Date]] = [:]
|
||||
|
||||
@@ -19,8 +19,10 @@ final class NoiseRateLimiter {
|
||||
private var globalMessageTimestamps: [Date] = []
|
||||
|
||||
private let queue = DispatchQueue(label: "chat.bitchat.noise.ratelimit", attributes: .concurrent)
|
||||
|
||||
func allowHandshake(from peerID: PeerID) -> Bool {
|
||||
|
||||
public init() {}
|
||||
|
||||
public func allowHandshake(from peerID: PeerID) -> Bool {
|
||||
return queue.sync(flags: .barrier) {
|
||||
let now = Date()
|
||||
let oneMinuteAgo = now.addingTimeInterval(-60)
|
||||
@@ -49,7 +51,7 @@ final class NoiseRateLimiter {
|
||||
}
|
||||
}
|
||||
|
||||
func allowMessage(from peerID: PeerID) -> Bool {
|
||||
public func allowMessage(from peerID: PeerID) -> Bool {
|
||||
return queue.sync(flags: .barrier) {
|
||||
let now = Date()
|
||||
let oneSecondAgo = now.addingTimeInterval(-1)
|
||||
@@ -85,7 +87,7 @@ final class NoiseRateLimiter {
|
||||
}
|
||||
}
|
||||
|
||||
func resetAll() {
|
||||
public func resetAll() {
|
||||
queue.async(flags: .barrier) {
|
||||
self.handshakeTimestamps.removeAll()
|
||||
self.messageTimestamps.removeAll()
|
||||
+5
-5
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum NoiseSecurityConstants {
|
||||
public enum NoiseSecurityConstants {
|
||||
// Maximum message size to prevent memory exhaustion
|
||||
static let maxMessageSize = 65535 // 64KB as per Noise spec
|
||||
|
||||
@@ -28,10 +28,10 @@ enum NoiseSecurityConstants {
|
||||
static let maxSessionsPerPeer = 3
|
||||
|
||||
// Rate limiting
|
||||
static let maxHandshakesPerMinute = 10
|
||||
static let maxMessagesPerSecond = 100
|
||||
public static let maxHandshakesPerMinute = 10
|
||||
public static let maxMessagesPerSecond = 100
|
||||
|
||||
// Global rate limiting (across all peers)
|
||||
static let maxGlobalHandshakesPerMinute = 30
|
||||
static let maxGlobalMessagesPerSecond = 500
|
||||
public static let maxGlobalHandshakesPerMinute = 30
|
||||
public static let maxGlobalMessagesPerSecond = 500
|
||||
}
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum NoiseSecurityError: Error {
|
||||
public enum NoiseSecurityError: Error {
|
||||
case sessionExpired
|
||||
case sessionExhausted
|
||||
case messageTooLarge
|
||||
+3
-3
@@ -8,15 +8,15 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct NoiseSecurityValidator {
|
||||
public struct NoiseSecurityValidator {
|
||||
|
||||
/// Validate message size
|
||||
static func validateMessageSize(_ data: Data) -> Bool {
|
||||
public static func validateMessageSize(_ data: Data) -> Bool {
|
||||
return data.count <= NoiseSecurityConstants.maxMessageSize
|
||||
}
|
||||
|
||||
/// Validate handshake message size
|
||||
static func validateHandshakeMessageSize(_ data: Data) -> Bool {
|
||||
public static func validateHandshakeMessageSize(_ data: Data) -> Bool {
|
||||
return data.count <= NoiseSecurityConstants.maxHandshakeMessageSize
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -11,10 +11,10 @@ import Foundation
|
||||
import CryptoKit
|
||||
import BitFoundation
|
||||
|
||||
class NoiseSession {
|
||||
public class NoiseSession {
|
||||
let peerID: PeerID
|
||||
let role: NoiseRole
|
||||
private let keychain: KeychainManagerProtocol
|
||||
private let keychain: SecureMemoryCleaner
|
||||
private var state: NoiseSessionState = .uninitialized
|
||||
private var handshakeState: NoiseHandshakeState?
|
||||
private var sendCipher: NoiseCipherState?
|
||||
@@ -34,7 +34,7 @@ class NoiseSession {
|
||||
init(
|
||||
peerID: PeerID,
|
||||
role: NoiseRole,
|
||||
keychain: KeychainManagerProtocol,
|
||||
keychain: SecureMemoryCleaner,
|
||||
localStaticKey: Curve25519.KeyAgreement.PrivateKey,
|
||||
remoteStaticKey: Curve25519.KeyAgreement.PublicKey? = nil
|
||||
) {
|
||||
@@ -182,7 +182,7 @@ class NoiseSession {
|
||||
}
|
||||
}
|
||||
|
||||
func isEstablished() -> Bool {
|
||||
public func isEstablished() -> Bool {
|
||||
return sessionQueue.sync {
|
||||
if case .established = state {
|
||||
return true
|
||||
@@ -217,8 +217,8 @@ class NoiseSession {
|
||||
sentHandshakeMessages.removeAll()
|
||||
|
||||
// Clear handshake hash
|
||||
if var hash = handshakeHash {
|
||||
keychain.secureClear(&hash)
|
||||
if handshakeHash != nil {
|
||||
keychain.secureClear(&handshakeHash!)
|
||||
}
|
||||
handshakeHash = nil
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
// For more information, see <https://unlicense.org>
|
||||
//
|
||||
|
||||
enum NoiseSessionError: Error, Equatable {
|
||||
public enum NoiseSessionError: Error, Equatable {
|
||||
case invalidState
|
||||
case notEstablished
|
||||
case sessionNotFound
|
||||
+15
-15
@@ -11,18 +11,18 @@ import CryptoKit
|
||||
import Foundation
|
||||
import BitFoundation
|
||||
|
||||
final class NoiseSessionManager {
|
||||
public final class NoiseSessionManager {
|
||||
private var sessions: [PeerID: NoiseSession] = [:]
|
||||
private let localStaticKey: Curve25519.KeyAgreement.PrivateKey
|
||||
private let keychain: KeychainManagerProtocol
|
||||
private let keychain: SecureMemoryCleaner
|
||||
private let sessionFactory: (PeerID, NoiseRole) -> NoiseSession
|
||||
private let managerQueue = DispatchQueue(label: "chat.bitchat.noise.manager", attributes: .concurrent)
|
||||
|
||||
// Callbacks
|
||||
var onSessionEstablished: ((PeerID, Curve25519.KeyAgreement.PublicKey) -> Void)?
|
||||
public var onSessionEstablished: ((PeerID, Curve25519.KeyAgreement.PublicKey) -> Void)?
|
||||
var onSessionFailed: ((PeerID, Error) -> Void)?
|
||||
|
||||
init(localStaticKey: Curve25519.KeyAgreement.PrivateKey, keychain: KeychainManagerProtocol) {
|
||||
public init(localStaticKey: Curve25519.KeyAgreement.PrivateKey, keychain: SecureMemoryCleaner) {
|
||||
self.localStaticKey = localStaticKey
|
||||
self.keychain = keychain
|
||||
self.sessionFactory = { peerID, role in
|
||||
@@ -38,7 +38,7 @@ final class NoiseSessionManager {
|
||||
#if DEBUG
|
||||
init(
|
||||
localStaticKey: Curve25519.KeyAgreement.PrivateKey,
|
||||
keychain: KeychainManagerProtocol,
|
||||
keychain: SecureMemoryCleaner,
|
||||
sessionFactory: @escaping (PeerID, NoiseRole) -> NoiseSession
|
||||
) {
|
||||
self.localStaticKey = localStaticKey
|
||||
@@ -49,13 +49,13 @@ final class NoiseSessionManager {
|
||||
|
||||
// MARK: - Session Management
|
||||
|
||||
func getSession(for peerID: PeerID) -> NoiseSession? {
|
||||
public func getSession(for peerID: PeerID) -> NoiseSession? {
|
||||
return managerQueue.sync {
|
||||
return sessions[peerID]
|
||||
}
|
||||
}
|
||||
|
||||
func removeSession(for peerID: PeerID) {
|
||||
public func removeSession(for peerID: PeerID) {
|
||||
managerQueue.sync(flags: .barrier) {
|
||||
if let session = sessions.removeValue(forKey: peerID) {
|
||||
session.reset() // Clear sensitive data before removing
|
||||
@@ -63,7 +63,7 @@ final class NoiseSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
func removeAllSessions() {
|
||||
public func removeAllSessions() {
|
||||
managerQueue.sync(flags: .barrier) {
|
||||
for (_, session) in sessions {
|
||||
session.reset()
|
||||
@@ -74,7 +74,7 @@ final class NoiseSessionManager {
|
||||
|
||||
// MARK: - Handshake Helpers
|
||||
|
||||
func initiateHandshake(with peerID: PeerID) throws -> Data {
|
||||
public func initiateHandshake(with peerID: PeerID) throws -> Data {
|
||||
return try managerQueue.sync(flags: .barrier) {
|
||||
// Check if we already have an established session
|
||||
if let existingSession = sessions[peerID], existingSession.isEstablished() {
|
||||
@@ -103,7 +103,7 @@ final class NoiseSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
func handleIncomingHandshake(from peerID: PeerID, message: Data) throws -> Data? {
|
||||
public func handleIncomingHandshake(from peerID: PeerID, message: Data) throws -> Data? {
|
||||
// Process everything within the synchronized block to prevent race conditions
|
||||
return try managerQueue.sync(flags: .barrier) {
|
||||
var shouldCreateNew = false
|
||||
@@ -173,7 +173,7 @@ final class NoiseSessionManager {
|
||||
|
||||
// MARK: - Encryption/Decryption
|
||||
|
||||
func encrypt(_ plaintext: Data, for peerID: PeerID) throws -> Data {
|
||||
public func encrypt(_ plaintext: Data, for peerID: PeerID) throws -> Data {
|
||||
guard let session = getSession(for: peerID) else {
|
||||
throw NoiseSessionError.sessionNotFound
|
||||
}
|
||||
@@ -181,7 +181,7 @@ final class NoiseSessionManager {
|
||||
return try session.encrypt(plaintext)
|
||||
}
|
||||
|
||||
func decrypt(_ ciphertext: Data, from peerID: PeerID) throws -> Data {
|
||||
public func decrypt(_ ciphertext: Data, from peerID: PeerID) throws -> Data {
|
||||
guard let session = getSession(for: peerID) else {
|
||||
throw NoiseSessionError.sessionNotFound
|
||||
}
|
||||
@@ -191,13 +191,13 @@ final class NoiseSessionManager {
|
||||
|
||||
// MARK: - Key Management
|
||||
|
||||
func getRemoteStaticKey(for peerID: PeerID) -> Curve25519.KeyAgreement.PublicKey? {
|
||||
public func getRemoteStaticKey(for peerID: PeerID) -> Curve25519.KeyAgreement.PublicKey? {
|
||||
return getSession(for: peerID)?.getRemoteStaticPublicKey()
|
||||
}
|
||||
|
||||
// MARK: - Session Rekeying
|
||||
|
||||
func getSessionsNeedingRekey() -> [(peerID: PeerID, needsRekey: Bool)] {
|
||||
public func getSessionsNeedingRekey() -> [(peerID: PeerID, needsRekey: Bool)] {
|
||||
return managerQueue.sync {
|
||||
var needingRekey: [(peerID: PeerID, needsRekey: Bool)] = []
|
||||
|
||||
@@ -213,7 +213,7 @@ final class NoiseSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
func initiateRekey(for peerID: PeerID) throws {
|
||||
public func initiateRekey(for peerID: PeerID) throws {
|
||||
// Remove old session
|
||||
removeSession(for: peerID)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// SecureMemoryCleaner.swift
|
||||
// bitchat
|
||||
//
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
// For more information, see <https://unlicense.org>
|
||||
//
|
||||
|
||||
import struct Foundation.Data
|
||||
|
||||
public protocol SecureMemoryCleaner {
|
||||
func secureClear(_ data: inout Data)
|
||||
func secureClear(_ string: inout String)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// MockKeychain.swift
|
||||
// bitchat
|
||||
//
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
// For more information, see <https://unlicense.org>
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@testable import Noise
|
||||
|
||||
// TODO: Combine with the one from the main target
|
||||
final class MockKeychain: SecureMemoryCleaner {
|
||||
/// Thread-safe counter for secureClear calls
|
||||
private let lock = NSLock()
|
||||
private var _secureClearDataCallCount = 0
|
||||
|
||||
var secureClearDataCallCount: Int {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return _secureClearDataCallCount
|
||||
}
|
||||
|
||||
func secureClear(_ data: inout Data) {
|
||||
lock.lock()
|
||||
_secureClearDataCallCount += 1
|
||||
lock.unlock()
|
||||
data = Data()
|
||||
}
|
||||
|
||||
func secureClear(_ string: inout String) {
|
||||
string = ""
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -2,8 +2,7 @@ import CryptoKit
|
||||
import Foundation
|
||||
import Testing
|
||||
import BitFoundation
|
||||
|
||||
@testable import bitchat
|
||||
@testable import Noise
|
||||
|
||||
@Suite("Noise Coverage Tests")
|
||||
struct NoiseCoverageTests {
|
||||
+6
-8
@@ -10,7 +10,7 @@ import CryptoKit
|
||||
import Foundation
|
||||
import Testing
|
||||
import BitFoundation
|
||||
@testable import bitchat
|
||||
@testable import Noise
|
||||
|
||||
// MARK: - Test Vector Support
|
||||
|
||||
@@ -613,9 +613,7 @@ struct NoiseProtocolTests {
|
||||
|
||||
private func loadTestVectors() throws -> [NoiseTestVector] {
|
||||
// Try to load from test bundle
|
||||
let testBundle = Bundle(for: MockKeychain.self)
|
||||
guard let url = testBundle.url(forResource: "NoiseTestVectors", withExtension: "json")
|
||||
else {
|
||||
guard let url = Bundle.module.url(forResource: "NoiseTestVectors", withExtension: "json") else {
|
||||
throw NSError(
|
||||
domain: "NoiseTests", code: 1,
|
||||
userInfo: [
|
||||
@@ -794,7 +792,7 @@ struct NoiseProtocolTests {
|
||||
|
||||
@Test func secureClearCalledDuringHandshake() throws {
|
||||
// Use TrackingMockKeychain to verify secureClear is called
|
||||
let trackingKeychain = TrackingMockKeychain()
|
||||
let trackingKeychain = MockKeychain()
|
||||
|
||||
let aliceKey = Curve25519.KeyAgreement.PrivateKey()
|
||||
let bobKey = Curve25519.KeyAgreement.PrivateKey()
|
||||
@@ -843,7 +841,7 @@ struct NoiseProtocolTests {
|
||||
|
||||
@Test func encryptionWorksAfterSecureClear() throws {
|
||||
// Verify that encryption/decryption still works correctly after adding secureClear
|
||||
let trackingKeychain = TrackingMockKeychain()
|
||||
let trackingKeychain = MockKeychain()
|
||||
|
||||
let aliceKey = Curve25519.KeyAgreement.PrivateKey()
|
||||
let bobKey = Curve25519.KeyAgreement.PrivateKey()
|
||||
@@ -900,8 +898,8 @@ struct NoiseProtocolTests {
|
||||
// Verify secureClear is called in both writeMessage and readMessage paths
|
||||
// We do this by checking the count increases at each step
|
||||
|
||||
let aliceKeychain = TrackingMockKeychain()
|
||||
let bobKeychain = TrackingMockKeychain()
|
||||
let aliceKeychain = MockKeychain()
|
||||
let bobKeychain = MockKeychain()
|
||||
|
||||
let aliceKey = Curve25519.KeyAgreement.PrivateKey()
|
||||
let bobKey = Curve25519.KeyAgreement.PrivateKey()
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import XCTest
|
||||
import BitFoundation
|
||||
@testable import bitchat
|
||||
@testable import Noise
|
||||
|
||||
final class NoiseRateLimiterTests: XCTestCase {
|
||||
func test_allowHandshake_blocksAfterPerPeerLimit() {
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// TestHelpers.swift
|
||||
// bitchatTests
|
||||
//
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
// For more information, see <https://unlicense.org>
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
import BitFoundation
|
||||
|
||||
// TODO: Combine with the one from the main target
|
||||
final class TestHelpers {
|
||||
static func generateRandomData(length: Int) -> Data {
|
||||
var data = Data(count: length)
|
||||
_ = data.withUnsafeMutableBytes { bytes in
|
||||
SecRandomCopyBytes(kSecRandomDefault, length, bytes.baseAddress!)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
@MainActor
|
||||
static func waitUntil(
|
||||
_ condition: @escaping () -> Bool,
|
||||
timeout: TimeInterval = 5,
|
||||
pollInterval: TimeInterval = 0.01
|
||||
) async -> Bool {
|
||||
let start = Date()
|
||||
while !condition() {
|
||||
if Date().timeIntervalSince(start) > timeout {
|
||||
return condition()
|
||||
}
|
||||
try? await sleep(pollInterval)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func sleep(_ seconds: TimeInterval) async throws {
|
||||
try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
|
||||
}
|
||||
Reference in New Issue
Block a user