From 009262602e313357a04161f593303f41ced47ba1 Mon Sep 17 00:00:00 2001 From: Rob Napier Date: Sun, 4 Oct 2015 10:43:54 -0400 Subject: [PATCH] Remove impossible throws --- README.md | 8 ++++- RNCryptor.swift | 34 ++++++++++----------- Tests/RNCryptorTests.swift | 60 ++++++++++++++++++++++++-------------- 3 files changed, 61 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 8704fdec..1ee2c3d6 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ if (error != nil) { ## Incremental Usage -RNCryptor suports incremental use, specifically designed to work with `NSURLSession`. This is also useful for cases where the encrypted or decrypted data will not comfortably fit in memory. +RNCryptor supports incremental use, for example when using with `NSURLSession`. This is also useful for cases where the encrypted or decrypted data will not comfortably fit in memory. To operate in incremental mode, you create an `Encryptor` or `Decryptor`, call `updateWithData()` repeatedly, gathering its results, and then call `finalData()` and gather its result. @@ -232,6 +232,12 @@ RNDecryptor *decryptor = [[[RNDecryptorV3 alloc] initWithEncryptionKey:encryptio ## FAQ +### How do I detect an incorrect password? + +If you decrypt with the wrong password, you will receive an `HMACMismatch` error. This is the same error you will receive if your ciphertext is corrupted. You won't receive this error until the entire message has been decrypted (during the call to `finalData()`). + +The v3 data format has no way to detect incorrect passwords directly. It just decrypts gibberish, and then uses the HMAC (a kind of encrypted hash) to determine that the result is corrupt. + ### Can I use RNCryptor to read and write my non-RNCryptor data format? No. RNCryptor implements a specific data format. It is not a general-purpose encryption library. If you have created your own data format, you will need to write specific code to deal with whatever you created. Please make sure the data format you've invented is secure. (This is much harder than it sounds.) diff --git a/RNCryptor.swift b/RNCryptor.swift index 89293233..f8f06c4c 100644 --- a/RNCryptor.swift +++ b/RNCryptor.swift @@ -326,14 +326,14 @@ public extension RNCryptor { /// - returns: Processed data. May be empty. public func updateWithData(data: NSData) -> NSData { // It should not be possible for this to fail during encryption - return try! handle(engine.updateWithData(data)) + return handle(engine.updateWithData(data)) } /// Returns trailing data and invalidates the cryptor. /// /// - returns: Trailing data public func finalData() -> NSData { - let result = NSMutableData(data: try! handle(engine.finalData())) + let result = NSMutableData(data: handle(engine.finalData())) result.appendData(hmac.finalData()) return result } @@ -440,7 +440,7 @@ public extension RNCryptor { /// - returns: Processed data. May be empty. public func updateWithData(data: NSData) throws -> NSData { if let e = decryptorEngine { - return try e.updateWithData(data) + return e.updateWithData(data) } buffer.appendData(data) @@ -452,7 +452,7 @@ public extension RNCryptor { decryptorEngine = e let body = buffer.bytesView[requiredHeaderSize.. NSData { + func updateWithData(data: NSData) -> NSData { let outputLength = sizeBufferForDataOfLength(data.length) var dataOutMoved: Int = 0 - var result: CCCryptorStatus = CCCryptorStatus(kCCUnimplemented) - - result = CCCryptorUpdate( + let result = CCCryptorUpdate( cryptor, data.bytes, data.length, buffer.mutableBytes, outputLength, &dataOutMoved) // The only error returned by CCCryptorUpdate is kCCBufferTooSmall, which would be a programming error - assert(result == CCCryptorStatus(kCCSuccess)) + assert(result == CCCryptorStatus(kCCSuccess), "RNCRYPTOR BUG. PLEASE REPORT.") buffer.length = dataOutMoved return buffer } - func finalData() throws -> NSData { + func finalData() -> NSData { let outputLength = sizeBufferForDataOfLength(0) var dataOutMoved: Int = 0 @@ -595,9 +591,11 @@ internal final class Engine { &dataOutMoved ) - guard result == CCCryptorStatus(kCCSuccess) else { - throw NSError(domain: CCErrorDomain, code: Int(result), userInfo: nil) - } + // Note that since iOS 6, CCryptor will never return padding errors or other decode errors. + // I'm not aware of any non-catestrophic (MemoryAllocation) situation in which this + // can fail. Using assert() just in case, but we'll ignore errors in Release. + // https://devforums.apple.com/message/920802#920802 + assert(result == CCCryptorStatus(kCCSuccess), "RNCRYPTOR BUG. PLEASE REPORT.") buffer.length = dataOutMoved return buffer @@ -626,14 +624,14 @@ private final class DecryptorEngineV3 { engine = Engine(operation: .Decrypt, key: encryptionKey, iv: iv) } - func updateWithData(data: NSData) throws -> NSData { + func updateWithData(data: NSData) -> NSData { let overflow = buffer.updateWithData(data) hmac.updateWithData(overflow) - return try engine.updateWithData(overflow) + return engine.updateWithData(overflow) } func finalData() throws -> NSData { - let result = try engine.finalData() + let result = engine.finalData() let hash = hmac.finalData() if !isEqualInConsistentTime(trusted: hash, untrusted: buffer.finalData()) { throw RNCryptorError.HMACMismatch diff --git a/Tests/RNCryptorTests.swift b/Tests/RNCryptorTests.swift index d2b94232..bd026b02 100644 --- a/Tests/RNCryptorTests.swift +++ b/Tests/RNCryptorTests.swift @@ -26,6 +26,14 @@ import XCTest @testable import RNCryptor +func randomLength() -> Int { + return Int(arc4random_uniform(1024) + 1) +} + +func randomData() -> NSData { + return RNCryptor.randomDataOfLength(randomLength()) +} + class RNCryptorTests: XCTestCase { func testRandomData() { let len = 1024 @@ -45,27 +53,19 @@ class RNCryptorTests: XCTestCase { } func testEngine() { - let data = RNCryptor.randomDataOfLength(1024) + let data = randomData() let encryptKey = RNCryptor.randomDataOfLength(V3.keySize) let iv = RNCryptor.randomDataOfLength(V3.ivSize) let encrypted = NSMutableData() - do { - let encryptor = Engine(operation: .Encrypt, key: encryptKey, iv: iv) - encrypted.appendData(try encryptor.updateWithData(data)) - encrypted.appendData(try encryptor.finalData()) - } catch { - XCTFail("Caught: \(error)") - } - - do { - let decryptor = Engine(operation: .Decrypt, key: encryptKey, iv: iv) - let decrypted = NSMutableData(data:try decryptor.updateWithData(encrypted)) - decrypted.appendData(try decryptor.finalData()) - XCTAssertEqual(decrypted, data) - } catch { - XCTFail("Caught: \(error)") - } + let encryptor = Engine(operation: .Encrypt, key: encryptKey, iv: iv) + encrypted.appendData(encryptor.updateWithData(data)) + encrypted.appendData(encryptor.finalData()) + + let decryptor = Engine(operation: .Decrypt, key: encryptKey, iv: iv) + let decrypted = NSMutableData(data:decryptor.updateWithData(encrypted)) + decrypted.appendData(decryptor.finalData()) + XCTAssertEqual(decrypted, data) } func testKeyEncryptor() { @@ -127,7 +127,7 @@ class RNCryptorTests: XCTestCase { func testOneShotKey() { let encryptionKey = RNCryptor.randomDataOfLength(V3.keySize) let hmacKey = RNCryptor.randomDataOfLength(V3.keySize) - let data = RNCryptor.randomDataOfLength(1024) + let data = randomData() let ciphertext = RNCryptor.EncryptorV3(encryptionKey: encryptionKey, hmacKey: hmacKey).encryptData(data) @@ -144,7 +144,7 @@ class RNCryptorTests: XCTestCase { func testOneShotPassword() { let password = "thepassword" - let data = RNCryptor.randomDataOfLength(1024) + let data = randomData() let ciphertext = RNCryptor.Encryptor(password: password).encryptData(data) @@ -161,7 +161,7 @@ class RNCryptorTests: XCTestCase { func testMultipleUpdateWithData() { let password = "thepassword" - let datas = (0..<10).map{ _ in RNCryptor.randomDataOfLength(1024) } + let datas = (0..<10).map{ _ in randomData() } let fullData = datas.reduce(NSMutableData()) { $0.appendData($1); return $0 } let encryptor = RNCryptor.Encryptor(password: password) @@ -180,7 +180,7 @@ class RNCryptorTests: XCTestCase { } func testBadFormat() { - let data = NSMutableData(length: 1024)! + let data = NSMutableData(length: randomLength())! do { try RNCryptor.Decryptor(password: "password").decryptData(data) XCTFail("Should not thrown") @@ -192,7 +192,7 @@ class RNCryptorTests: XCTestCase { } func testBadFormatV3() { - let data = NSMutableData(length: 1024)! + let data = NSMutableData(length: randomLength())! do { try RNCryptor.DecryptorV3(password: "password").decryptData(data) XCTFail("Should not thrown") @@ -202,4 +202,20 @@ class RNCryptorTests: XCTestCase { XCTFail("Threw wrong thing \(error)") } } + + func testBadPassword() { + let password = "thepassword" + let data = randomData() + + let ciphertext = RNCryptor.Encryptor(password: password).encryptData(data) + + do { + let _ = try RNCryptor.Decryptor(password: "wrongpassword").decryptData(ciphertext) + XCTFail("Should have failed to decrypt") + } catch let err as RNCryptorError { + XCTAssertEqual(err, RNCryptorError.HMACMismatch) + } catch { + XCTFail("Wrong error: \(error)") + } + } }