mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 08:45:19 +00:00
Merge pull request #291 from permissionlesstech/cleanup-dead-code
Remove dead code and fix warnings
This commit is contained in:
@@ -37,10 +37,8 @@
|
|||||||
04B6BA572E203D6C0090FE39 /* FingerprintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA532E203D6C0090FE39 /* FingerprintView.swift */; };
|
04B6BA572E203D6C0090FE39 /* FingerprintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA532E203D6C0090FE39 /* FingerprintView.swift */; };
|
||||||
04B6BA582E203D6C0090FE39 /* NoiseTestingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA542E203D6C0090FE39 /* NoiseTestingView.swift */; };
|
04B6BA582E203D6C0090FE39 /* NoiseTestingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA542E203D6C0090FE39 /* NoiseTestingView.swift */; };
|
||||||
04B6BA5B2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA5A2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift */; };
|
04B6BA5B2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA5A2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift */; };
|
||||||
04B6BA5B2E215FDA0090FE39 /* NoisePostQuantum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA5A2E215FDA0090FE39 /* NoisePostQuantum.swift */; };
|
|
||||||
04B6BA5C2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA5A2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift */; };
|
04B6BA5C2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA5A2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift */; };
|
||||||
04B6BA5C2E215FDA0090FE39 /* NoiseChannelKeyRotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA592E215FDA0090FE39 /* NoiseChannelKeyRotation.swift */; };
|
04B6BA5C2E215FDA0090FE39 /* NoiseChannelKeyRotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA592E215FDA0090FE39 /* NoiseChannelKeyRotation.swift */; };
|
||||||
04B6BA5D2E215FDA0090FE39 /* NoisePostQuantum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA5A2E215FDA0090FE39 /* NoisePostQuantum.swift */; };
|
|
||||||
04B6BA5E2E215FDA0090FE39 /* NoiseChannelKeyRotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA592E215FDA0090FE39 /* NoiseChannelKeyRotation.swift */; };
|
04B6BA5E2E215FDA0090FE39 /* NoiseChannelKeyRotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA592E215FDA0090FE39 /* NoiseChannelKeyRotation.swift */; };
|
||||||
04B6BA672E21601B0090FE39 /* ChannelVerificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA5F2E21601B0090FE39 /* ChannelVerificationTests.swift */; };
|
04B6BA672E21601B0090FE39 /* ChannelVerificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA5F2E21601B0090FE39 /* ChannelVerificationTests.swift */; };
|
||||||
04B6BA682E21601B0090FE39 /* NoiseKeyRotationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA622E21601B0090FE39 /* NoiseKeyRotationTests.swift */; };
|
04B6BA682E21601B0090FE39 /* NoiseKeyRotationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA622E21601B0090FE39 /* NoiseKeyRotationTests.swift */; };
|
||||||
@@ -166,7 +164,6 @@
|
|||||||
04B6BA542E203D6C0090FE39 /* NoiseTestingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoiseTestingView.swift; sourceTree = "<group>"; };
|
04B6BA542E203D6C0090FE39 /* NoiseTestingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoiseTestingView.swift; sourceTree = "<group>"; };
|
||||||
04B6BA592E215FDA0090FE39 /* NoiseChannelKeyRotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoiseChannelKeyRotation.swift; sourceTree = "<group>"; };
|
04B6BA592E215FDA0090FE39 /* NoiseChannelKeyRotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoiseChannelKeyRotation.swift; sourceTree = "<group>"; };
|
||||||
04B6BA5A2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoiseIdentityPersistenceTests.swift; sourceTree = "<group>"; };
|
04B6BA5A2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoiseIdentityPersistenceTests.swift; sourceTree = "<group>"; };
|
||||||
04B6BA5A2E215FDA0090FE39 /* NoisePostQuantum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoisePostQuantum.swift; sourceTree = "<group>"; };
|
|
||||||
04B6BA5F2E21601B0090FE39 /* ChannelVerificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelVerificationTests.swift; sourceTree = "<group>"; };
|
04B6BA5F2E21601B0090FE39 /* ChannelVerificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelVerificationTests.swift; sourceTree = "<group>"; };
|
||||||
04B6BA602E21601B0090FE39 /* KeychainIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainIntegrationTests.swift; sourceTree = "<group>"; };
|
04B6BA602E21601B0090FE39 /* KeychainIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainIntegrationTests.swift; sourceTree = "<group>"; };
|
||||||
04B6BA612E21601B0090FE39 /* NoiseChannelEncryptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoiseChannelEncryptionTests.swift; sourceTree = "<group>"; };
|
04B6BA612E21601B0090FE39 /* NoiseChannelEncryptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoiseChannelEncryptionTests.swift; sourceTree = "<group>"; };
|
||||||
@@ -218,7 +215,6 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
04B6BA592E215FDA0090FE39 /* NoiseChannelKeyRotation.swift */,
|
04B6BA592E215FDA0090FE39 /* NoiseChannelKeyRotation.swift */,
|
||||||
04B6BA5A2E215FDA0090FE39 /* NoisePostQuantum.swift */,
|
|
||||||
04B6BA402E2035530090FE39 /* NoiseChannelEncryption.swift */,
|
04B6BA402E2035530090FE39 /* NoiseChannelEncryption.swift */,
|
||||||
04B6BA412E2035530090FE39 /* NoiseProtocol.swift */,
|
04B6BA412E2035530090FE39 /* NoiseProtocol.swift */,
|
||||||
04B6BA422E2035530090FE39 /* NoiseSecurityConsiderations.swift */,
|
04B6BA422E2035530090FE39 /* NoiseSecurityConsiderations.swift */,
|
||||||
@@ -555,7 +551,6 @@
|
|||||||
4B747085D07A1BCE0F5BA612 /* BinaryProtocol.swift in Sources */,
|
4B747085D07A1BCE0F5BA612 /* BinaryProtocol.swift in Sources */,
|
||||||
6E7761E21C99F28AE2F9BE5F /* BitchatApp.swift in Sources */,
|
6E7761E21C99F28AE2F9BE5F /* BitchatApp.swift in Sources */,
|
||||||
04891CAA2E22971E0064A111 /* LRUCache.swift in Sources */,
|
04891CAA2E22971E0064A111 /* LRUCache.swift in Sources */,
|
||||||
04B6BA5B2E215FDA0090FE39 /* NoisePostQuantum.swift in Sources */,
|
|
||||||
04B6BA5C2E215FDA0090FE39 /* NoiseChannelKeyRotation.swift in Sources */,
|
04B6BA5C2E215FDA0090FE39 /* NoiseChannelKeyRotation.swift in Sources */,
|
||||||
923027D6F2F417AFA2488127 /* BitchatProtocol.swift in Sources */,
|
923027D6F2F417AFA2488127 /* BitchatProtocol.swift in Sources */,
|
||||||
04B6BA452E2035530090FE39 /* NoiseSecurityConsiderations.swift in Sources */,
|
04B6BA452E2035530090FE39 /* NoiseSecurityConsiderations.swift in Sources */,
|
||||||
@@ -591,7 +586,6 @@
|
|||||||
F455F011B3B648ADA233F998 /* BinaryProtocol.swift in Sources */,
|
F455F011B3B648ADA233F998 /* BinaryProtocol.swift in Sources */,
|
||||||
10E68BB889356219189E38EC /* BitchatApp.swift in Sources */,
|
10E68BB889356219189E38EC /* BitchatApp.swift in Sources */,
|
||||||
04891CA92E22971E0064A111 /* LRUCache.swift in Sources */,
|
04891CA92E22971E0064A111 /* LRUCache.swift in Sources */,
|
||||||
04B6BA5D2E215FDA0090FE39 /* NoisePostQuantum.swift in Sources */,
|
|
||||||
04B6BA5E2E215FDA0090FE39 /* NoiseChannelKeyRotation.swift in Sources */,
|
04B6BA5E2E215FDA0090FE39 /* NoiseChannelKeyRotation.swift in Sources */,
|
||||||
6DE056E1EE9850E9FBF50157 /* BitchatProtocol.swift in Sources */,
|
6DE056E1EE9850E9FBF50157 /* BitchatProtocol.swift in Sources */,
|
||||||
04B6BA492E2035530090FE39 /* NoiseSecurityConsiderations.swift in Sources */,
|
04B6BA492E2035530090FE39 /* NoiseSecurityConsiderations.swift in Sources */,
|
||||||
|
|||||||
@@ -293,37 +293,4 @@ class NoiseChannelKeyRotation {
|
|||||||
_ = KeychainManager.shared.deleteChannelPassword(for: epochKey)
|
_ = KeychainManager.shared.deleteChannelPassword(for: epochKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Future Double Ratchet Support
|
|
||||||
|
|
||||||
/// Placeholder for full Double Ratchet implementation
|
|
||||||
/// This would handle per-message key derivation and ratcheting
|
|
||||||
protocol DoubleRatchetProtocol {
|
|
||||||
/// Initialize a new ratchet session
|
|
||||||
func initializeRatchet(sharedSecret: Data, isInitiator: Bool) throws
|
|
||||||
|
|
||||||
/// Ratchet forward and get next message key
|
|
||||||
func ratchetEncrypt(_ plaintext: Data) throws -> (ciphertext: Data, header: Data)
|
|
||||||
|
|
||||||
/// Ratchet forward using received header and decrypt
|
|
||||||
func ratchetDecrypt(_ ciphertext: Data, header: Data) throws -> Data
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Message header for Double Ratchet (future use)
|
|
||||||
struct RatchetHeader: Codable {
|
|
||||||
let publicKey: Data // Ephemeral public key
|
|
||||||
let previousChainLength: UInt32
|
|
||||||
let messageNumber: UInt32
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Placeholder for full implementation
|
|
||||||
class ChannelDoubleRatchet {
|
|
||||||
// This would implement the full Double Ratchet algorithm
|
|
||||||
// adapted for multi-party channels
|
|
||||||
// Challenges:
|
|
||||||
// - Sender keys for multi-party
|
|
||||||
// - Out-of-order delivery
|
|
||||||
// - State synchronization
|
|
||||||
// - Performance with many members
|
|
||||||
}
|
}
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
//
|
|
||||||
// NoisePostQuantum.swift
|
|
||||||
// bitchat
|
|
||||||
//
|
|
||||||
// This is free and unencumbered software released into the public domain.
|
|
||||||
// For more information, see <https://unlicense.org>
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CryptoKit
|
|
||||||
|
|
||||||
// MARK: - Post-Quantum Cryptography Framework
|
|
||||||
|
|
||||||
/// Framework for integrating post-quantum algorithms with Noise Protocol
|
|
||||||
/// Currently a placeholder until PQ libraries are available in Swift/iOS
|
|
||||||
protocol PostQuantumKeyExchange {
|
|
||||||
associatedtype PublicKey
|
|
||||||
associatedtype PrivateKey
|
|
||||||
associatedtype SharedSecret
|
|
||||||
|
|
||||||
/// Generate a new keypair
|
|
||||||
static func generateKeyPair() throws -> (publicKey: PublicKey, privateKey: PrivateKey)
|
|
||||||
|
|
||||||
/// Derive shared secret (for initiator)
|
|
||||||
static func encapsulate(remotePublicKey: PublicKey) throws -> (sharedSecret: SharedSecret, ciphertext: Data)
|
|
||||||
|
|
||||||
/// Derive shared secret (for responder)
|
|
||||||
static func decapsulate(ciphertext: Data, privateKey: PrivateKey) throws -> SharedSecret
|
|
||||||
|
|
||||||
/// Get size requirements
|
|
||||||
static var publicKeySize: Int { get }
|
|
||||||
static var privateKeySize: Int { get }
|
|
||||||
static var ciphertextSize: Int { get }
|
|
||||||
static var sharedSecretSize: Int { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Hybrid Key Exchange
|
|
||||||
|
|
||||||
/// Combines classical (Curve25519) with post-quantum algorithms
|
|
||||||
class HybridNoiseKeyExchange {
|
|
||||||
|
|
||||||
enum Algorithm {
|
|
||||||
case classicalOnly // Current: Curve25519 only
|
|
||||||
case hybridKyber768 // Future: Curve25519 + Kyber768
|
|
||||||
case hybridKyber1024 // Future: Curve25519 + Kyber1024
|
|
||||||
|
|
||||||
var name: String {
|
|
||||||
switch self {
|
|
||||||
case .classicalOnly:
|
|
||||||
return "25519"
|
|
||||||
case .hybridKyber768:
|
|
||||||
return "25519+Kyber768"
|
|
||||||
case .hybridKyber1024:
|
|
||||||
return "25519+Kyber1024"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isPostQuantum: Bool {
|
|
||||||
switch self {
|
|
||||||
case .classicalOnly:
|
|
||||||
return false
|
|
||||||
case .hybridKyber768, .hybridKyber1024:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct HybridPublicKey {
|
|
||||||
let classical: Curve25519.KeyAgreement.PublicKey
|
|
||||||
let postQuantum: Data? // Future: actual PQ public key
|
|
||||||
|
|
||||||
var serialized: Data {
|
|
||||||
var data = classical.rawRepresentation
|
|
||||||
if let pq = postQuantum {
|
|
||||||
data.append(pq)
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct HybridPrivateKey {
|
|
||||||
let classical: Curve25519.KeyAgreement.PrivateKey
|
|
||||||
let postQuantum: Data? // Future: actual PQ private key
|
|
||||||
}
|
|
||||||
|
|
||||||
struct HybridSharedSecret {
|
|
||||||
let classical: SharedSecret
|
|
||||||
let postQuantum: Data? // Future: actual PQ shared secret
|
|
||||||
|
|
||||||
/// Combine both secrets using KDF
|
|
||||||
func combinedSecret() -> SymmetricKey {
|
|
||||||
var combinedData = classical.withUnsafeBytes { Data($0) }
|
|
||||||
|
|
||||||
if let pq = postQuantum {
|
|
||||||
combinedData.append(pq)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use HKDF to combine secrets
|
|
||||||
let hash = SHA256.hash(data: combinedData)
|
|
||||||
return SymmetricKey(data: Data(hash))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Key Generation
|
|
||||||
|
|
||||||
static func generateKeyPair(algorithm: Algorithm) throws -> (publicKey: HybridPublicKey, privateKey: HybridPrivateKey) {
|
|
||||||
// Generate classical keypair
|
|
||||||
let classicalPrivate = Curve25519.KeyAgreement.PrivateKey()
|
|
||||||
let classicalPublic = classicalPrivate.publicKey
|
|
||||||
|
|
||||||
// Generate PQ keypair when available
|
|
||||||
let pqPublic: Data? = nil
|
|
||||||
let pqPrivate: Data? = nil
|
|
||||||
|
|
||||||
switch algorithm {
|
|
||||||
case .classicalOnly:
|
|
||||||
break // No PQ component
|
|
||||||
|
|
||||||
case .hybridKyber768, .hybridKyber1024:
|
|
||||||
// Future: Generate Kyber keypair
|
|
||||||
// let (pqPub, pqPriv) = try KyberKeyExchange.generateKeyPair()
|
|
||||||
// pqPublic = pqPub.serialized
|
|
||||||
// pqPrivate = pqPriv.serialized
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
HybridPublicKey(classical: classicalPublic, postQuantum: pqPublic),
|
|
||||||
HybridPrivateKey(classical: classicalPrivate, postQuantum: pqPrivate)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Key Agreement
|
|
||||||
|
|
||||||
static func performKeyAgreement(
|
|
||||||
localPrivate: HybridPrivateKey,
|
|
||||||
remotePublic: HybridPublicKey,
|
|
||||||
algorithm: Algorithm
|
|
||||||
) throws -> HybridSharedSecret {
|
|
||||||
// Perform classical ECDH
|
|
||||||
let classicalShared = try localPrivate.classical.sharedSecretFromKeyAgreement(
|
|
||||||
with: remotePublic.classical
|
|
||||||
)
|
|
||||||
|
|
||||||
// Perform PQ key agreement when available
|
|
||||||
let pqShared: Data? = nil
|
|
||||||
|
|
||||||
switch algorithm {
|
|
||||||
case .classicalOnly:
|
|
||||||
break // No PQ component
|
|
||||||
|
|
||||||
case .hybridKyber768, .hybridKyber1024:
|
|
||||||
// Future: Perform Kyber decapsulation
|
|
||||||
// if let pqCiphertext = remotePublic.postQuantum,
|
|
||||||
// let pqPrivateKey = localPrivate.postQuantum {
|
|
||||||
// pqShared = try KyberKeyExchange.decapsulate(
|
|
||||||
// ciphertext: pqCiphertext,
|
|
||||||
// privateKey: pqPrivateKey
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return HybridSharedSecret(classical: classicalShared, postQuantum: pqShared)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Modified Noise Pattern for PQ
|
|
||||||
|
|
||||||
/// Extended Noise handshake pattern for post-quantum
|
|
||||||
/// Based on Noise PQ patterns: https://github.com/noiseprotocol/noise_pq_spec
|
|
||||||
struct NoisePQHandshakePattern {
|
|
||||||
// Pattern modifiers for PQ
|
|
||||||
enum Modifier {
|
|
||||||
case pq1 // First message includes PQ KEM
|
|
||||||
case pq2 // Second message includes PQ KEM
|
|
||||||
case pq3 // Third message includes PQ KEM
|
|
||||||
}
|
|
||||||
|
|
||||||
// Example: XXpq1 pattern (XX with PQ in first message)
|
|
||||||
// -> e, epq
|
|
||||||
// <- e, ee, eepq, s, es
|
|
||||||
// -> s, se
|
|
||||||
|
|
||||||
// This would modify the Noise XX pattern to include
|
|
||||||
// post-quantum key encapsulation material
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Migration Support
|
|
||||||
|
|
||||||
/// Helps transition from classical to post-quantum crypto
|
|
||||||
class NoiseProtocolMigration {
|
|
||||||
|
|
||||||
enum MigrationPhase {
|
|
||||||
case classicalOnly // Current state
|
|
||||||
case hybridOptional // Support both, prefer hybrid
|
|
||||||
case hybridRequired // Require hybrid mode
|
|
||||||
case postQuantumOnly // Future: PQ only
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MigrationConfig {
|
|
||||||
let currentPhase: MigrationPhase
|
|
||||||
let preferredAlgorithm: HybridNoiseKeyExchange.Algorithm
|
|
||||||
let acceptedAlgorithms: Set<HybridNoiseKeyExchange.Algorithm>
|
|
||||||
let migrationDeadline: Date?
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if a peer supports post-quantum
|
|
||||||
static func checkPQSupport(peerVersion: String) -> Bool {
|
|
||||||
// Future: Parse peer capabilities
|
|
||||||
// For now, assume no PQ support
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get migration configuration
|
|
||||||
static func getMigrationConfig() -> MigrationConfig {
|
|
||||||
// Current configuration: classical only
|
|
||||||
return MigrationConfig(
|
|
||||||
currentPhase: .classicalOnly,
|
|
||||||
preferredAlgorithm: .classicalOnly,
|
|
||||||
acceptedAlgorithms: [.classicalOnly],
|
|
||||||
migrationDeadline: nil
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Future Implementation Notes
|
|
||||||
|
|
||||||
/*
|
|
||||||
Post-Quantum Integration Plan:
|
|
||||||
|
|
||||||
1. Wait for stable Swift PQ libraries (e.g., SwiftOQS)
|
|
||||||
2. Implement Kyber768/1024 wrapper conforming to PostQuantumKeyExchange
|
|
||||||
3. Update Noise handshake to support hybrid mode
|
|
||||||
4. Add capability negotiation in protocol
|
|
||||||
5. Implement gradual rollout with fallback
|
|
||||||
|
|
||||||
Challenges:
|
|
||||||
- Increased message sizes (Kyber768 public key ~1184 bytes)
|
|
||||||
- Performance impact on mobile devices
|
|
||||||
- Battery usage considerations
|
|
||||||
- Backward compatibility
|
|
||||||
- Library availability and maintenance
|
|
||||||
|
|
||||||
Timeline estimate:
|
|
||||||
- PQ libraries stable in Swift: 2025-2026
|
|
||||||
- Initial hybrid implementation: 2026
|
|
||||||
- Full deployment: 2027+
|
|
||||||
*/
|
|
||||||
|
|
||||||
// MARK: - Testing Support
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
/// Mock PQ implementation for testing
|
|
||||||
class MockPostQuantumKeyExchange: PostQuantumKeyExchange {
|
|
||||||
typealias PublicKey = Data
|
|
||||||
typealias PrivateKey = Data
|
|
||||||
typealias SharedSecret = Data
|
|
||||||
|
|
||||||
static func generateKeyPair() throws -> (publicKey: Data, privateKey: Data) {
|
|
||||||
// Generate mock keys for testing
|
|
||||||
let privateKey = Data((0..<32).map { _ in UInt8.random(in: 0...255) })
|
|
||||||
let publicKey = Data((0..<800).map { _ in UInt8.random(in: 0...255) }) // Simulate larger PQ key
|
|
||||||
return (publicKey, privateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func encapsulate(remotePublicKey: Data) throws -> (sharedSecret: Data, ciphertext: Data) {
|
|
||||||
let sharedSecret = Data((0..<32).map { _ in UInt8.random(in: 0...255) })
|
|
||||||
let ciphertext = Data((0..<1088).map { _ in UInt8.random(in: 0...255) }) // Simulate Kyber ciphertext
|
|
||||||
return (sharedSecret, ciphertext)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func decapsulate(ciphertext: Data, privateKey: Data) throws -> Data {
|
|
||||||
// Return deterministic secret based on inputs for testing
|
|
||||||
let combined = ciphertext + privateKey
|
|
||||||
let hash = SHA256.hash(data: combined)
|
|
||||||
return Data(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
static var publicKeySize: Int { 800 }
|
|
||||||
static var privateKeySize: Int { 1632 }
|
|
||||||
static var ciphertextSize: Int { 1088 }
|
|
||||||
static var sharedSecretSize: Int { 32 }
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -254,18 +254,18 @@ class NoiseSessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func removeSession(for peerID: String) {
|
func removeSession(for peerID: String) {
|
||||||
_ = managerQueue.sync(flags: .barrier) {
|
managerQueue.sync(flags: .barrier) {
|
||||||
sessions.removeValue(forKey: peerID)
|
_ = sessions.removeValue(forKey: peerID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func migrateSession(from oldPeerID: String, to newPeerID: String) {
|
func migrateSession(from oldPeerID: String, to newPeerID: String) {
|
||||||
_ = managerQueue.sync(flags: .barrier) {
|
managerQueue.sync(flags: .barrier) {
|
||||||
// Check if we have a session for the old peer ID
|
// Check if we have a session for the old peer ID
|
||||||
if let session = sessions[oldPeerID] {
|
if let session = sessions[oldPeerID] {
|
||||||
// Move the session to the new peer ID
|
// Move the session to the new peer ID
|
||||||
sessions[newPeerID] = session
|
sessions[newPeerID] = session
|
||||||
sessions.removeValue(forKey: oldPeerID)
|
_ = sessions.removeValue(forKey: oldPeerID)
|
||||||
|
|
||||||
SecurityLogger.log("Migrated Noise session from \(oldPeerID) to \(newPeerID)", category: SecurityLogger.noise, level: .info)
|
SecurityLogger.log("Migrated Noise session from \(oldPeerID) to \(newPeerID)", category: SecurityLogger.noise, level: .info)
|
||||||
}
|
}
|
||||||
@@ -290,7 +290,7 @@ class NoiseSessionManager {
|
|||||||
|
|
||||||
// Remove any existing non-established session
|
// Remove any existing non-established session
|
||||||
if let existingSession = sessions[peerID], !existingSession.isEstablished() {
|
if let existingSession = sessions[peerID], !existingSession.isEstablished() {
|
||||||
sessions.removeValue(forKey: peerID)
|
_ = sessions.removeValue(forKey: peerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new initiator session
|
// Create new initiator session
|
||||||
@@ -306,7 +306,7 @@ class NoiseSessionManager {
|
|||||||
return handshakeData
|
return handshakeData
|
||||||
} catch {
|
} catch {
|
||||||
// Clean up failed session
|
// Clean up failed session
|
||||||
sessions.removeValue(forKey: peerID)
|
_ = sessions.removeValue(forKey: peerID)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,7 +328,7 @@ class NoiseSessionManager {
|
|||||||
// If we're in the middle of a handshake and receive a new initiation,
|
// If we're in the middle of a handshake and receive a new initiation,
|
||||||
// reset and start fresh (the other side may have restarted)
|
// reset and start fresh (the other side may have restarted)
|
||||||
if existing.getState() == .handshaking && message.count == 32 {
|
if existing.getState() == .handshaking && message.count == 32 {
|
||||||
sessions.removeValue(forKey: peerID)
|
_ = sessions.removeValue(forKey: peerID)
|
||||||
shouldCreateNew = true
|
shouldCreateNew = true
|
||||||
} else {
|
} else {
|
||||||
existingSession = existing
|
existingSession = existing
|
||||||
@@ -369,7 +369,7 @@ class NoiseSessionManager {
|
|||||||
return response
|
return response
|
||||||
} catch {
|
} catch {
|
||||||
// Reset the session on handshake failure so next attempt can start fresh
|
// Reset the session on handshake failure so next attempt can start fresh
|
||||||
sessions.removeValue(forKey: peerID)
|
_ = sessions.removeValue(forKey: peerID)
|
||||||
|
|
||||||
// Schedule callback outside the synchronized block to prevent deadlock
|
// Schedule callback outside the synchronized block to prevent deadlock
|
||||||
DispatchQueue.global().async { [weak self] in
|
DispatchQueue.global().async { [weak self] in
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ struct MessagePadding {
|
|||||||
|
|
||||||
enum MessageType: UInt8 {
|
enum MessageType: UInt8 {
|
||||||
case announce = 0x01
|
case announce = 0x01
|
||||||
// 0x02 was legacy keyExchange - removed
|
|
||||||
case leave = 0x03
|
case leave = 0x03
|
||||||
case message = 0x04 // All user messages (private and broadcast)
|
case message = 0x04 // All user messages (private and broadcast)
|
||||||
case fragmentStart = 0x05
|
case fragmentStart = 0x05
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ class BluetoothMeshService: NSObject {
|
|||||||
private var activePeers: Set<String> = [] // Track all active peers
|
private var activePeers: Set<String> = [] // Track all active peers
|
||||||
private var peerRSSI: [String: NSNumber] = [:] // Track RSSI values for peers
|
private var peerRSSI: [String: NSNumber] = [:] // Track RSSI values for peers
|
||||||
private var peripheralRSSI: [String: NSNumber] = [:] // Track RSSI by peripheral ID during discovery
|
private var peripheralRSSI: [String: NSNumber] = [:] // Track RSSI by peripheral ID during discovery
|
||||||
private var loggedCryptoErrors = Set<String>() // Track which peers we've logged crypto errors for
|
|
||||||
|
|
||||||
// MARK: - Peer Identity Rotation
|
// MARK: - Peer Identity Rotation
|
||||||
// Mappings between ephemeral peer IDs and permanent fingerprints
|
// Mappings between ephemeral peer IDs and permanent fingerprints
|
||||||
@@ -884,8 +883,8 @@ class BluetoothMeshService: NSObject {
|
|||||||
private func notifyPeerIDChange(oldPeerID: String, newPeerID: String, fingerprint: String) {
|
private func notifyPeerIDChange(oldPeerID: String, newPeerID: String, fingerprint: String) {
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
// Remove old peer ID from active peers and announcedPeers
|
// Remove old peer ID from active peers and announcedPeers
|
||||||
_ = self?.collectionsQueue.sync(flags: .barrier) {
|
self?.collectionsQueue.sync(flags: .barrier) {
|
||||||
self?.activePeers.remove(oldPeerID)
|
_ = self?.activePeers.remove(oldPeerID)
|
||||||
// Don't pre-insert the new peer ID - let the announce packet handle it
|
// Don't pre-insert the new peer ID - let the announce packet handle it
|
||||||
// This ensures the connect message logic works properly
|
// This ensures the connect message logic works properly
|
||||||
}
|
}
|
||||||
@@ -2043,7 +2042,7 @@ class BluetoothMeshService: NSObject {
|
|||||||
// IMPORTANT: Remove old peer ID from activePeers to prevent duplicates
|
// IMPORTANT: Remove old peer ID from activePeers to prevent duplicates
|
||||||
collectionsQueue.sync(flags: .barrier) {
|
collectionsQueue.sync(flags: .barrier) {
|
||||||
if self.activePeers.contains(tempID) {
|
if self.activePeers.contains(tempID) {
|
||||||
self.activePeers.remove(tempID)
|
_ = self.activePeers.remove(tempID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2160,8 +2159,8 @@ class BluetoothMeshService: NSObject {
|
|||||||
if String(data: packet.payload, encoding: .utf8) != nil {
|
if String(data: packet.payload, encoding: .utf8) != nil {
|
||||||
// Remove from active peers with proper locking
|
// Remove from active peers with proper locking
|
||||||
collectionsQueue.sync(flags: .barrier) {
|
collectionsQueue.sync(flags: .barrier) {
|
||||||
self.activePeers.remove(senderID)
|
_ = self.activePeers.remove(senderID)
|
||||||
self.peerNicknames.removeValue(forKey: senderID)
|
_ = self.peerNicknames.removeValue(forKey: senderID)
|
||||||
}
|
}
|
||||||
|
|
||||||
announcedPeers.remove(senderID)
|
announcedPeers.remove(senderID)
|
||||||
@@ -2848,8 +2847,8 @@ extension BluetoothMeshService: CBCentralManagerDelegate {
|
|||||||
if removed {
|
if removed {
|
||||||
}
|
}
|
||||||
|
|
||||||
announcedPeers.remove(peerID)
|
_ = announcedPeers.remove(peerID)
|
||||||
announcedToPeers.remove(peerID)
|
_ = announcedToPeers.remove(peerID)
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3646,7 +3645,21 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
SecurityLogger.log("Version negotiation rejected by \(peerID): \(ack.reason ?? "Unknown reason")",
|
SecurityLogger.log("Version negotiation rejected by \(peerID): \(ack.reason ?? "Unknown reason")",
|
||||||
category: SecurityLogger.session, level: .error)
|
category: SecurityLogger.session, level: .error)
|
||||||
versionNegotiationState[peerID] = .failed(reason: ack.reason ?? "Version rejected")
|
versionNegotiationState[peerID] = .failed(reason: ack.reason ?? "Version rejected")
|
||||||
// TODO: Handle disconnection
|
|
||||||
|
// Clean up state for incompatible peer
|
||||||
|
collectionsQueue.sync(flags: .barrier) {
|
||||||
|
_ = self.activePeers.remove(peerID)
|
||||||
|
_ = self.peerNicknames.removeValue(forKey: peerID)
|
||||||
|
}
|
||||||
|
announcedPeers.remove(peerID)
|
||||||
|
|
||||||
|
// Clean up any Noise session
|
||||||
|
noiseService.removePeer(peerID)
|
||||||
|
|
||||||
|
// Notify delegate about incompatible peer disconnection
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.delegate?.didDisconnectFromPeer(peerID)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
SecurityLogger.log("Version negotiation successful with \(peerID): agreed on version \(ack.agreedVersion)",
|
SecurityLogger.log("Version negotiation successful with \(peerID): agreed on version \(ack.agreedVersion)",
|
||||||
category: SecurityLogger.session, level: .debug)
|
category: SecurityLogger.session, level: .debug)
|
||||||
@@ -3900,7 +3913,7 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
// Clean up old entries after 10 seconds
|
// Clean up old entries after 10 seconds
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) { [weak self] in
|
||||||
self?.collectionsQueue.sync(flags: .barrier) {
|
self?.collectionsQueue.sync(flags: .barrier) {
|
||||||
self?.recentlySentMessages.remove(sendKey)
|
_ = self?.recentlySentMessages.remove(sendKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -466,6 +466,4 @@ struct NoiseMessage: Codable {
|
|||||||
enum NoiseEncryptionError: Error {
|
enum NoiseEncryptionError: Error {
|
||||||
case handshakeRequired
|
case handshakeRequired
|
||||||
case sessionNotEstablished
|
case sessionNotEstablished
|
||||||
case invalidMessage
|
|
||||||
case handshakeFailed(Error)
|
|
||||||
}
|
}
|
||||||
@@ -307,80 +307,4 @@ class NoiseKeyRotationTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertTrue(decrypted, "Failed to decrypt with any valid key")
|
XCTAssertTrue(decrypted, "Failed to decrypt with any valid key")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Post-Quantum Framework Tests
|
|
||||||
|
|
||||||
class NoisePostQuantumTests: XCTestCase {
|
|
||||||
|
|
||||||
func testHybridKeyGeneration() throws {
|
|
||||||
let (publicKey, privateKey) = try HybridNoiseKeyExchange.generateKeyPair(algorithm: .classicalOnly)
|
|
||||||
|
|
||||||
XCTAssertNotNil(publicKey.classical)
|
|
||||||
XCTAssertNil(publicKey.postQuantum) // No PQ component yet
|
|
||||||
XCTAssertEqual(publicKey.serialized.count, 32) // Just Curve25519
|
|
||||||
|
|
||||||
XCTAssertNotNil(privateKey.classical)
|
|
||||||
XCTAssertNil(privateKey.postQuantum)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testHybridKeyAgreement() throws {
|
|
||||||
// Generate two keypairs
|
|
||||||
let (alicePub, alicePriv) = try HybridNoiseKeyExchange.generateKeyPair(algorithm: .classicalOnly)
|
|
||||||
let (bobPub, bobPriv) = try HybridNoiseKeyExchange.generateKeyPair(algorithm: .classicalOnly)
|
|
||||||
|
|
||||||
// Perform key agreement
|
|
||||||
let aliceShared = try HybridNoiseKeyExchange.performKeyAgreement(
|
|
||||||
localPrivate: alicePriv,
|
|
||||||
remotePublic: bobPub,
|
|
||||||
algorithm: .classicalOnly
|
|
||||||
)
|
|
||||||
|
|
||||||
let bobShared = try HybridNoiseKeyExchange.performKeyAgreement(
|
|
||||||
localPrivate: bobPriv,
|
|
||||||
remotePublic: alicePub,
|
|
||||||
algorithm: .classicalOnly
|
|
||||||
)
|
|
||||||
|
|
||||||
// Shared secrets should match
|
|
||||||
XCTAssertEqual(
|
|
||||||
aliceShared.combinedSecret().withUnsafeBytes { Data($0) },
|
|
||||||
bobShared.combinedSecret().withUnsafeBytes { Data($0) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMigrationConfig() {
|
|
||||||
let config = NoiseProtocolMigration.getMigrationConfig()
|
|
||||||
|
|
||||||
XCTAssertEqual(config.currentPhase, .classicalOnly)
|
|
||||||
XCTAssertEqual(config.preferredAlgorithm, .classicalOnly)
|
|
||||||
XCTAssertTrue(config.acceptedAlgorithms.contains(.classicalOnly))
|
|
||||||
XCTAssertNil(config.migrationDeadline)
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
func testMockPostQuantum() throws {
|
|
||||||
// Test mock PQ implementation
|
|
||||||
let (publicKey, privateKey) = try MockPostQuantumKeyExchange.generateKeyPair()
|
|
||||||
|
|
||||||
XCTAssertEqual(publicKey.count, MockPostQuantumKeyExchange.publicKeySize)
|
|
||||||
XCTAssertEqual(privateKey.count, 32) // Mock uses smaller private key
|
|
||||||
|
|
||||||
let (sharedSecret, ciphertext) = try MockPostQuantumKeyExchange.encapsulate(
|
|
||||||
remotePublicKey: publicKey
|
|
||||||
)
|
|
||||||
|
|
||||||
XCTAssertEqual(ciphertext.count, MockPostQuantumKeyExchange.ciphertextSize)
|
|
||||||
XCTAssertEqual(sharedSecret.count, MockPostQuantumKeyExchange.sharedSecretSize)
|
|
||||||
|
|
||||||
let decapsulatedSecret = try MockPostQuantumKeyExchange.decapsulate(
|
|
||||||
ciphertext: ciphertext,
|
|
||||||
privateKey: privateKey
|
|
||||||
)
|
|
||||||
|
|
||||||
// In real implementation, these would match
|
|
||||||
// Mock just returns deterministic values
|
|
||||||
XCTAssertEqual(decapsulatedSecret.count, 32)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user