mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 11:05:19 +00:00
This major update replaces the basic encryption with the Noise Protocol Framework and adds ephemeral peer ID rotation for enhanced privacy. Key Changes: Security Infrastructure: - Implemented Noise Protocol Framework (XX handshake pattern) - End-to-end encryption with forward secrecy and identity hiding - Session management with automatic rekey support - Channel encryption with password-derived keys Privacy Enhancements: - Ephemeral peer ID rotation (5-15 minute random intervals) - Persistent identity through public key fingerprints - Favorites and verification persist across ID rotations - Block list based on fingerprints, not ephemeral IDs Core Components Added: - NoiseEncryptionService: Main encryption service - NoiseSession: Individual peer session management - NoiseChannelEncryption: Password-protected channel support - SecureIdentityStateManager: Persistent identity storage - FingerprintView: Visual fingerprint verification UI Bug Fixes: - Fixed handshake storm with tie-breaker mechanism - Fixed missing connect messages during peer rotation - Fixed delivery ACK compression issues - Fixed race conditions in message queue - Fixed nickname resolution for rotated peer IDs Testing: - Comprehensive test suite for Noise implementation - Security validator tests - Channel encryption tests - Identity persistence tests - Rate limiter tests Documentation: - BRING_THE_NOISE.md: Technical implementation details - Updated WHITEPAPER.md: Simplified and focused on core innovations - Removed temporary debug documentation The implementation maintains backward compatibility while significantly improving security and privacy. All existing features (channels, private messages, favorites, blocking) work seamlessly with the new system.
247 lines
9.0 KiB
Swift
247 lines
9.0 KiB
Swift
//
|
|
// MessagePaddingTests.swift
|
|
// bitchatTests
|
|
//
|
|
// This is free and unencumbered software released into the public domain.
|
|
// For more information, see <https://unlicense.org>
|
|
//
|
|
|
|
import XCTest
|
|
@testable import bitchat
|
|
|
|
class MessagePaddingTests: XCTestCase {
|
|
|
|
func testBasicPadding() {
|
|
let originalData = Data("Hello".utf8)
|
|
let targetSize = 256
|
|
|
|
let padded = MessagePadding.pad(originalData, toSize: targetSize)
|
|
XCTAssertEqual(padded.count, targetSize)
|
|
|
|
let unpadded = MessagePadding.unpad(padded)
|
|
XCTAssertEqual(unpadded, originalData)
|
|
}
|
|
|
|
func testMultipleBlockSizes() {
|
|
let testMessages = [
|
|
"Hi",
|
|
"This is a longer message",
|
|
"This is an even longer message that should require a larger block size",
|
|
String(repeating: "A", count: 500)
|
|
]
|
|
|
|
for message in testMessages {
|
|
let data = Data(message.utf8)
|
|
let blockSize = MessagePadding.optimalBlockSize(for: data.count)
|
|
|
|
// Block size should be reasonable
|
|
XCTAssertGreaterThan(blockSize, data.count)
|
|
XCTAssertTrue(MessagePadding.blockSizes.contains(blockSize) || blockSize == data.count)
|
|
|
|
let padded = MessagePadding.pad(data, toSize: blockSize)
|
|
|
|
// Check if padding was applied (only if needed padding <= 255)
|
|
let paddingNeeded = blockSize - data.count
|
|
if paddingNeeded <= 255 {
|
|
XCTAssertEqual(padded.count, blockSize)
|
|
let unpadded = MessagePadding.unpad(padded)
|
|
XCTAssertEqual(unpadded, data)
|
|
} else {
|
|
// No padding applied if more than 255 bytes needed
|
|
XCTAssertEqual(padded, data)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testPaddingWithLargeData() {
|
|
let largeData = Data(repeating: 0xFF, count: 1500)
|
|
let blockSize = MessagePadding.optimalBlockSize(for: largeData.count)
|
|
|
|
// Should use 2048 block
|
|
XCTAssertEqual(blockSize, 2048)
|
|
|
|
let padded = MessagePadding.pad(largeData, toSize: blockSize)
|
|
// Since padding needed (548 bytes) > 255, no padding is applied
|
|
XCTAssertEqual(padded.count, largeData.count)
|
|
XCTAssertEqual(padded, largeData)
|
|
|
|
// Test with data that fits within PKCS#7 limits
|
|
let smallerData = Data(repeating: 0xAA, count: 1800)
|
|
let paddedSmaller = MessagePadding.pad(smallerData, toSize: 2048)
|
|
// Padding needed is 248 bytes, which is < 255, so padding should work
|
|
XCTAssertEqual(paddedSmaller.count, 2048)
|
|
|
|
let unpaddedSmaller = MessagePadding.unpad(paddedSmaller)
|
|
XCTAssertEqual(unpaddedSmaller, smallerData)
|
|
}
|
|
|
|
func testInvalidPadding() {
|
|
// Test empty data
|
|
let empty = Data()
|
|
let unpaddedEmpty = MessagePadding.unpad(empty)
|
|
XCTAssertEqual(unpaddedEmpty, empty)
|
|
|
|
// Test data with invalid padding length
|
|
var invalidPadding = Data(repeating: 0x00, count: 100)
|
|
invalidPadding[99] = 255 // Invalid padding length
|
|
let result = MessagePadding.unpad(invalidPadding)
|
|
XCTAssertEqual(result, invalidPadding) // Should return original if invalid
|
|
}
|
|
|
|
func testPaddingRandomness() {
|
|
// Ensure padding bytes are random (not predictable)
|
|
let data = Data("Test".utf8)
|
|
let padded1 = MessagePadding.pad(data, toSize: 256)
|
|
let padded2 = MessagePadding.pad(data, toSize: 256)
|
|
|
|
// Same size
|
|
XCTAssertEqual(padded1.count, padded2.count)
|
|
|
|
// But different padding bytes (with very high probability)
|
|
XCTAssertNotEqual(padded1, padded2)
|
|
|
|
// Both should unpad to same data
|
|
XCTAssertEqual(MessagePadding.unpad(padded1), data)
|
|
XCTAssertEqual(MessagePadding.unpad(padded2), data)
|
|
}
|
|
|
|
// MARK: - Edge Case Tests
|
|
|
|
func testExactBlockSizeData() {
|
|
// Test data that exactly matches block sizes
|
|
for blockSize in MessagePadding.blockSizes {
|
|
// Account for 16 bytes encryption overhead
|
|
let dataSize = blockSize - 16
|
|
let data = Data(repeating: 0x42, count: dataSize)
|
|
|
|
let optimalSize = MessagePadding.optimalBlockSize(for: data.count)
|
|
XCTAssertEqual(optimalSize, blockSize)
|
|
|
|
// Should fit exactly, no padding needed
|
|
let padded = MessagePadding.pad(data, toSize: blockSize)
|
|
XCTAssertEqual(padded.count, blockSize)
|
|
}
|
|
}
|
|
|
|
func testOneByteOverBlockSize() {
|
|
// Test data that's one byte over block size threshold
|
|
let blockSizes = [256, 512, 1024]
|
|
|
|
for blockSize in blockSizes {
|
|
// Create data that's 1 byte too large for current block
|
|
let dataSize = blockSize - 16 + 1
|
|
let data = Data(repeating: 0x42, count: dataSize)
|
|
|
|
let optimalSize = MessagePadding.optimalBlockSize(for: data.count)
|
|
|
|
// Should jump to next block size
|
|
if blockSize < 2048 {
|
|
XCTAssertGreaterThan(optimalSize, blockSize)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testVerySmallData() {
|
|
// Test tiny messages
|
|
let tinyMessages = [
|
|
Data([0x01]),
|
|
Data([0x01, 0x02]),
|
|
Data("a".utf8),
|
|
Data()
|
|
]
|
|
|
|
for data in tinyMessages {
|
|
let blockSize = MessagePadding.optimalBlockSize(for: data.count)
|
|
XCTAssertEqual(blockSize, 256) // Should use minimum block size
|
|
|
|
if !data.isEmpty {
|
|
let padded = MessagePadding.pad(data, toSize: blockSize)
|
|
XCTAssertEqual(padded.count, blockSize)
|
|
|
|
let unpadded = MessagePadding.unpad(padded)
|
|
XCTAssertEqual(unpadded, data)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testPaddingBoundaryConditions() {
|
|
// Test PKCS#7 padding limit (255 bytes)
|
|
let testCases = [
|
|
(dataSize: 1, targetSize: 256), // Need 255 bytes padding - exactly at limit
|
|
(dataSize: 2, targetSize: 256), // Need 254 bytes padding - just under limit
|
|
(dataSize: 256, targetSize: 512), // Need 256 bytes padding - just over limit
|
|
]
|
|
|
|
for testCase in testCases {
|
|
let data = Data(repeating: 0x42, count: testCase.dataSize)
|
|
let padded = MessagePadding.pad(data, toSize: testCase.targetSize)
|
|
|
|
let paddingNeeded = testCase.targetSize - testCase.dataSize
|
|
if paddingNeeded <= 255 {
|
|
// Padding should be applied
|
|
XCTAssertEqual(padded.count, testCase.targetSize)
|
|
|
|
// Verify correct padding byte value
|
|
let paddingByte = padded[padded.count - 1]
|
|
XCTAssertEqual(Int(paddingByte), paddingNeeded)
|
|
|
|
// Should unpad correctly
|
|
let unpadded = MessagePadding.unpad(padded)
|
|
XCTAssertEqual(unpadded, data)
|
|
} else {
|
|
// No padding applied
|
|
XCTAssertEqual(padded, data)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testCorruptedPadding() {
|
|
let data = Data("Test message".utf8)
|
|
let padded = MessagePadding.pad(data, toSize: 256)
|
|
|
|
// Corrupt the padding length byte
|
|
var corrupted = padded
|
|
corrupted[corrupted.count - 1] = 0
|
|
|
|
let result = MessagePadding.unpad(corrupted)
|
|
XCTAssertEqual(result, corrupted) // Should return original when padding is invalid
|
|
|
|
// Test with padding length > data size
|
|
var corruptedTooLarge = padded
|
|
corruptedTooLarge[corruptedTooLarge.count - 1] = 255
|
|
|
|
let result2 = MessagePadding.unpad(corruptedTooLarge)
|
|
XCTAssertEqual(result2, corruptedTooLarge)
|
|
}
|
|
|
|
func testDataAlreadyLargerThanTarget() {
|
|
let data = Data(repeating: 0x42, count: 1000)
|
|
let tooSmallTarget = 256
|
|
|
|
// Should return original data when it's already larger than target
|
|
let result = MessagePadding.pad(data, toSize: tooSmallTarget)
|
|
XCTAssertEqual(result, data)
|
|
XCTAssertEqual(result.count, data.count)
|
|
}
|
|
|
|
func testOptimalBlockSizeForLargeData() {
|
|
// Test data larger than largest block size
|
|
let hugeData = Data(repeating: 0x42, count: 5000)
|
|
let blockSize = MessagePadding.optimalBlockSize(for: hugeData.count)
|
|
|
|
// Should return data size when larger than all blocks
|
|
XCTAssertEqual(blockSize, hugeData.count)
|
|
}
|
|
|
|
func testPaddingPerformance() {
|
|
let data = Data(repeating: 0x42, count: 1000)
|
|
|
|
measure {
|
|
for _ in 0..<1000 {
|
|
let blockSize = MessagePadding.optimalBlockSize(for: data.count)
|
|
let padded = MessagePadding.pad(data, toSize: blockSize)
|
|
_ = MessagePadding.unpad(padded)
|
|
}
|
|
}
|
|
}
|
|
} |