From b97241978d03263232668bbd5b2ce9f448a6aad3 Mon Sep 17 00:00:00 2001 From: islam <2553451+qalandarov@users.noreply.github.com> Date: Sat, 20 Sep 2025 01:26:17 +0100 Subject: [PATCH] Unified `Peer` validation --- bitchat/Models/Peer.swift | 42 +++++++++++++++++-- bitchat/Models/ReadReceipt.swift | 2 +- .../Noise/NoiseSecurityConsiderations.swift | 5 --- bitchat/Services/NoiseEncryptionService.swift | 4 +- bitchat/Utils/InputValidator.swift | 21 ---------- bitchat/Utils/PeerIDResolver.swift | 8 ---- 6 files changed, 41 insertions(+), 41 deletions(-) diff --git a/bitchat/Models/Peer.swift b/bitchat/Models/Peer.swift index ae0e96d5..eee9ae12 100644 --- a/bitchat/Models/Peer.swift +++ b/bitchat/Models/Peer.swift @@ -10,10 +10,6 @@ import Foundation struct Peer: Equatable, Hashable { let id: String - - var isShort: Bool { - id.count == 16 && Data(hexString: id) != nil - } } extension Peer { @@ -30,6 +26,44 @@ extension Peer { } } +// MARK: - Validation + +extension Peer { + private enum Constants { + static let maxIDLength = 64 + static let hexIDLength = 16 // 8 bytes = 16 hex chars + } + + /// Validates a peer ID from any source (short 16-hex, full 64-hex, or internal alnum/-/_ up to 64) + var isValid: Bool { + // Accept short routing IDs (exact 16-hex) or Full Noise key hex (exact 64-hex) + if isShort || isNoiseKeyHex { + return true + } + + // If length equals short or full but isn't valid hex, reject + if id.count == Constants.hexIDLength || id.count == Constants.maxIDLength { + return false + } + + // Internal format: alphanumeric + dash/underscore up to 63 (not 16 or 64) + let validCharset = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-_")) + return !id.isEmpty && + id.count < Constants.maxIDLength && + id.rangeOfCharacter(from: validCharset.inverted) == nil + } + + /// Short routing IDs (exact 16-hex) + var isShort: Bool { + id.count == Constants.hexIDLength && Data(hexString: id) != nil + } + + /// Full Noise key hex (exact 64-hex) + var isNoiseKeyHex: Bool { + id.count == Constants.maxIDLength && Data(hexString: id) != nil + } +} + // MARK: - ExpressibleByStringLiteral extension Peer: ExpressibleByStringLiteral { diff --git a/bitchat/Models/ReadReceipt.swift b/bitchat/Models/ReadReceipt.swift index 2e261dae..04aefd95 100644 --- a/bitchat/Models/ReadReceipt.swift +++ b/bitchat/Models/ReadReceipt.swift @@ -79,7 +79,7 @@ struct ReadReceipt: Codable { guard let readerIDData = dataCopy.readFixedBytes(at: &offset, count: 8) else { return nil } let readerID = readerIDData.hexEncodedString() - guard InputValidator.validatePeerID(readerID) else { return nil } + guard Peer(str: readerID).isValid else { return nil } guard let timestamp = dataCopy.readDate(at: &offset), InputValidator.validateTimestamp(timestamp), diff --git a/bitchat/Noise/NoiseSecurityConsiderations.swift b/bitchat/Noise/NoiseSecurityConsiderations.swift index a9e69216..fd747c17 100644 --- a/bitchat/Noise/NoiseSecurityConsiderations.swift +++ b/bitchat/Noise/NoiseSecurityConsiderations.swift @@ -53,11 +53,6 @@ struct NoiseSecurityValidator { static func validateHandshakeMessageSize(_ data: Data) -> Bool { return data.count <= NoiseSecurityConstants.maxHandshakeMessageSize } - - /// Validate peer ID format using unified validator - static func validatePeerID(_ peerID: String) -> Bool { - return InputValidator.validatePeerID(peerID) - } } // MARK: - Enhanced Noise Session with Security diff --git a/bitchat/Services/NoiseEncryptionService.swift b/bitchat/Services/NoiseEncryptionService.swift index 7d55421d..69b24fd1 100644 --- a/bitchat/Services/NoiseEncryptionService.swift +++ b/bitchat/Services/NoiseEncryptionService.swift @@ -394,7 +394,7 @@ final class NoiseEncryptionService { func initiateHandshake(with peerID: String) throws -> Data { // Validate peer ID - guard NoiseSecurityValidator.validatePeerID(peerID) else { + guard Peer(str: peerID).isValid else { SecureLogger.warning(.authenticationFailed(peerID: peerID)) throw NoiseSecurityError.invalidPeerID } @@ -417,7 +417,7 @@ final class NoiseEncryptionService { func processHandshakeMessage(from peerID: String, message: Data) throws -> Data? { // Validate peer ID - guard NoiseSecurityValidator.validatePeerID(peerID) else { + guard Peer(str: peerID).isValid else { SecureLogger.warning(.authenticationFailed(peerID: peerID)) throw NoiseSecurityError.invalidPeerID } diff --git a/bitchat/Utils/InputValidator.swift b/bitchat/Utils/InputValidator.swift index c2323cfc..f186dc7b 100644 --- a/bitchat/Utils/InputValidator.swift +++ b/bitchat/Utils/InputValidator.swift @@ -10,27 +10,6 @@ struct InputValidator { static let maxNicknameLength = 50 static let maxMessageLength = 10_000 static let maxReasonLength = 200 - static let maxPeerIDLength = 64 - static let hexPeerIDLength = 16 // 8 bytes = 16 hex chars - } - - // MARK: - Peer ID Validation - - /// Validates a peer ID from any source (short 16-hex, full 64-hex, or internal alnum/-/_ up to 64) - static func validatePeerID(_ peerID: String) -> Bool { - // Accept short routing IDs (exact 16-hex) - if PeerIDResolver.isShortID(peerID) { return true } - // If length equals short-hex length but isn't valid hex, reject - if peerID.count == Limits.hexPeerIDLength { return false } - // Accept full Noise key hex (exact 64-hex) - if PeerIDResolver.isNoiseKeyHex(peerID) { return true } - // If length equals full key length but isn't valid hex, reject - if peerID.count == Limits.maxPeerIDLength { return false } - // Internal format: alphanumeric + dash/underscore up to 63 (not 16 or 64) - let validCharset = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-_")) - return !peerID.isEmpty && - peerID.count < Limits.maxPeerIDLength && - peerID.rangeOfCharacter(from: validCharset.inverted) == nil } // MARK: - String Content Validation diff --git a/bitchat/Utils/PeerIDResolver.swift b/bitchat/Utils/PeerIDResolver.swift index 1411d59e..151b3aba 100644 --- a/bitchat/Utils/PeerIDResolver.swift +++ b/bitchat/Utils/PeerIDResolver.swift @@ -8,13 +8,5 @@ struct PeerIDResolver { } return id } - - static func isShortID(_ id: String) -> Bool { - return id.count == 16 && Data(hexString: id) != nil - } - - static func isNoiseKeyHex(_ id: String) -> Bool { - return id.count == 64 && Data(hexString: id) != nil - } }