From c4d32539db44ccfc874e32d56809194f5e45b03f Mon Sep 17 00:00:00 2001 From: Alexandre Garrefa Date: Sun, 10 Feb 2019 19:25:27 +0100 Subject: [PATCH] WIP: Add RSAES-OAEP support (#95) * Add RSAES_OAEP type to AsymmetricKeyAlgorithm * Adds AsymmetricKeyAlgorithm.RSAES_OAEP and implements plain text and cipher length check * Add encrypter for (RSAES_OAEP, A256CBCHS512) * Add decrypter for (RSAES_OAEP, A256CBCHS512) * Rename new AsymmetricKeyAlgorithm case to RSAOAEP * Fix length checks * Add Encrypter Tests * Add Decrypter Tests --- JOSESwift/Sources/Algorithms.swift | 2 + .../Sources/CryptoImplementation/RSA.swift | 30 +++- JOSESwift/Sources/Decrypter.swift | 22 +-- JOSESwift/Sources/Encrypter.swift | 6 +- Tests/RSADecrypterTests.swift | 168 ++++++++++++++++-- Tests/RSAEncrypterTests.swift | 142 ++++++++++++++- 6 files changed, 332 insertions(+), 38 deletions(-) diff --git a/JOSESwift/Sources/Algorithms.swift b/JOSESwift/Sources/Algorithms.swift index c18042a4..8aa20ba6 100644 --- a/JOSESwift/Sources/Algorithms.swift +++ b/JOSESwift/Sources/Algorithms.swift @@ -34,9 +34,11 @@ public enum SignatureAlgorithm: String { /// An algorithm for asymmetric encryption and decryption. /// /// - RSA1_5: [RSAES-PKCS1-v1_5](https://tools.ietf.org/html/rfc7518#section-4.2) +/// - RSAES-OAEP: [RSAES-OAEP](https://tools.ietf.org/html/rfc7518#section-4.3) /// - direct: [Direct Encryption with a Shared Symmetric Key](https://tools.ietf.org/html/rfc7518#section-4.5) public enum AsymmetricKeyAlgorithm: String { case RSA1_5 = "RSA1_5" + case RSAOAEP = "RSAES-OAEP" case direct = "dir" } diff --git a/JOSESwift/Sources/CryptoImplementation/RSA.swift b/JOSESwift/Sources/CryptoImplementation/RSA.swift index c699c21e..1690f206 100644 --- a/JOSESwift/Sources/CryptoImplementation/RSA.swift +++ b/JOSESwift/Sources/CryptoImplementation/RSA.swift @@ -50,7 +50,9 @@ fileprivate extension AsymmetricKeyAlgorithm { switch self { case .RSA1_5: return .rsaEncryptionPKCS1 - default: + case .RSAOAEP: + return .rsaEncryptionOAEPSHA1 + case .direct: return nil } } @@ -58,12 +60,21 @@ fileprivate extension AsymmetricKeyAlgorithm { /// Checks if the plain text length does not exceed the maximum /// for the chosen algorithm and the corresponding public key. func isPlainTextLengthSatisfied(_ plainText: Data, for publicKey: SecKey) -> Bool { + let mLen: Int = plainText.count + let k = SecKeyGetBlockSize(publicKey) switch self { case .RSA1_5: // For detailed information about the allowed plain text length for RSAES-PKCS1-v1_5, - // please refer to the RFC(https://tools.ietf.org/html/rfc3447#section-7.2). - return plainText.count <= (SecKeyGetBlockSize(publicKey) - 11) - default: + // please refer to the RFC(https://tools.ietf.org/html/rfc3447#section-7.2.1). + return mLen <= (k - 11) + case .RSAOAEP: + // For detailed information about the allowed plain text length for RSAOAEP, + // please refer to the RFC (https://tools.ietf.org/html/rfc3447#section-7.1.1 + // and https://tools.ietf.org/html/rfc3174#section-1) + // hLen = input limitation for the hash function (20 octets for SHA-1) + let hLen = 20 + return mLen <= (k - 2 * hLen - 2) + case .direct: return false } } @@ -71,8 +82,17 @@ fileprivate extension AsymmetricKeyAlgorithm { func isCipherTextLenghtSatisfied(_ cipherText: Data, for privateKey: SecKey) -> Bool { switch self { case .RSA1_5: + // For detailed information about the allowed cipher length for RSAES-PKCS1-v1_5, + // please refer to the RFC(https://tools.ietf.org/html/rfc3447#section-7.2.2). return cipherText.count == SecKeyGetBlockSize(privateKey) - default: + case .RSAOAEP: + // For detailed information about the allowed cipher length for RSAOAEP, + // please refer to the RFC(https://tools.ietf.org/html/rfc3447#section-7.1.2 + // https://tools.ietf.org/html/rfc3174#section-1, + // and https://www.rfc-editor.org/errata_search.php?rfc=3447) + // C: ciphertext to be decrypted, an octet string of length k, where k >= 2hLen + 2 + return cipherText.count == SecKeyGetBlockSize(privateKey) + case .direct: return false } } diff --git a/JOSESwift/Sources/Decrypter.swift b/JOSESwift/Sources/Decrypter.swift index fc80e153..d776a993 100644 --- a/JOSESwift/Sources/Decrypter.swift +++ b/JOSESwift/Sources/Decrypter.swift @@ -79,7 +79,7 @@ public struct Decrypter { /// - Returns: A fully initialized `Decrypter` or `nil` if provided key is of the wrong type. public init?(keyDecryptionAlgorithm: AsymmetricKeyAlgorithm, decryptionKey key: KeyType, contentDecryptionAlgorithm: SymmetricKeyAlgorithm) { switch (keyDecryptionAlgorithm, contentDecryptionAlgorithm) { - case (.RSA1_5, .A256CBCHS512): + case (.RSA1_5, .A256CBCHS512), (.RSAOAEP, .A256CBCHS512): guard type(of: key) is RSADecrypter.KeyType.Type else { return nil } @@ -89,7 +89,7 @@ public struct Decrypter { case (.direct, .A256CBCHS512): guard type(of: key) is AESDecrypter.KeyType.Type else { return nil - } + } self.asymmetric = RSADecrypter(algorithm: keyDecryptionAlgorithm) // swiftlint:disable:next force_cast @@ -135,15 +135,15 @@ public struct Decrypter { cek = symmetricKey } else { - // Generate random CEK to prevent MMA (Million Message Attack). - // For detailed information, please refer to this RFC(https://tools.ietf.org/html/rfc3218#section-2.3.2) - // and http://www.ietf.org/mail-archive/web/jose/current/msg01832.html - let randomCEK = try SecureRandom.generate(count: enc.keyLength) - - if let decryptedCEK = try? asymmetric.decrypt(context.encryptedKey) { - cek = decryptedCEK - } else { - cek = randomCEK + // Generate random CEK to prevent MMA (Million Message Attack). + // For detailed information, please refer to this RFC(https://tools.ietf.org/html/rfc3218#section-2.3.2) + // and http://www.ietf.org/mail-archive/web/jose/current/msg01832.html + let randomCEK = try SecureRandom.generate(count: enc.keyLength) + + if let decryptedCEK = try? asymmetric.decrypt(context.encryptedKey) { + cek = decryptedCEK + } else { + cek = randomCEK } } diff --git a/JOSESwift/Sources/Encrypter.swift b/JOSESwift/Sources/Encrypter.swift index 089b6d5c..26d561e9 100644 --- a/JOSESwift/Sources/Encrypter.swift +++ b/JOSESwift/Sources/Encrypter.swift @@ -80,7 +80,7 @@ public struct Encrypter { /// - Returns: A fully initialized `Encrypter` or `nil` if provided key is of the wrong type. public init?(keyEncryptionAlgorithm: AsymmetricKeyAlgorithm, encryptionKey key: KeyType, contentEncyptionAlgorithm: SymmetricKeyAlgorithm) { switch (keyEncryptionAlgorithm, contentEncyptionAlgorithm) { - case (.RSA1_5, .A256CBCHS512): + case (.RSA1_5, .A256CBCHS512), (.RSAOAEP, .A256CBCHS512): guard type(of: key) is RSAEncrypter.KeyType.Type else { return nil } @@ -90,12 +90,12 @@ public struct Encrypter { case (.direct, .A256CBCHS512): guard type(of: key) is AESEncrypter.KeyType.Type else { return nil - } + } self.asymmetric = RSAEncrypter(algorithm: keyEncryptionAlgorithm) // swiftlint:disable:next force_cast self.symmetric = AESEncrypter(algorithm: contentEncyptionAlgorithm, symmetricKey: (key as! AESEncrypter.KeyType)) - } + } } /// Constructs an encrypter used to encrypt a JWE. diff --git a/Tests/RSADecrypterTests.swift b/Tests/RSADecrypterTests.swift index f4def81f..9ed4d74e 100644 --- a/Tests/RSADecrypterTests.swift +++ b/Tests/RSADecrypterTests.swift @@ -34,6 +34,12 @@ class RSADecrypterTests: CryptoTestCase { gurwC3C0X+Q3W1itUlq6fH4xpRMTnp19VCqSw2i9+/yBdwLriCOzG2K5bOaGbC/e1CgtV2c26uLW0zkj6Aw2F5dFttFbVi+AXEBv3L1H3iXOT6lH2Dv5luQ\ fu/lA9mQbFoKNjp+0WHSMB3jmRdX9mC4GoIPP8vQKaCa8cNw5RxtP2M4TjMPJQYrnRn3Jsx0rSxPaBse9HyOtr43QH4B51VLyExmNHWyNSt28wFTav+EaBx\ KwawQvhC/447MoBlhtE3bYolvfu5vY3uFV/Dh8Ip5zRvZuE6NwRZN2EdWyR35iphyCgcKufJn9J1oYYZ0b2Sgbrw1e0naWkgYm6djXFw== +""" + // printf "The true sign of intelligence is not knowledge but imagination." | openssl rsautl -encrypt -oaep -pubin -inkey alice.pub.pem -out >(base64) + let cipherTextWithAliceKeyBase64OAEP = """ +S55OZCsH+Vg3J2xRqmwQj4CG58c0gHfvxhJOgAKWkWd3N7m+jbn7NRo35ZoP1q/dwutnTQxHg4S9S68NpfY22mOOtHR6wiMz5M/ll6f+EKhwwFQOFzXAS8m\ +ihiFINYSdp8BjayvxAlee4HiTygH+clw8nxXu64soHx23Q1kDqqqYUdO/7xzM/WrsN0Be4RoRnqDgX/mxC4AnjHaGA2TlW/Uv+8qHRrO/4OfmByJMW2tkpD\ +jEhB7NQ34ig4RohAw+eNsgC9Tm7KBQEsTlv6A4wPFHbYRMbK5W2HGuICOUV+xfVuzyoPhK5Jl6bg9GQKD15VkyRlEkZRy2FPRF+ETuEA== """ // printf "The true sign of intelligence is not knowledge but imagination." | openssl rsautl -encrypt -pubin -inkey bob.pub.pem -out >(base64) @@ -41,9 +47,16 @@ KwawQvhC/447MoBlhtE3bYolvfu5vY3uFV/Dh8Ip5zRvZuE6NwRZN2EdWyR35iphyCgcKufJn9J1oYYZ TA13QruprKdRMt6JVE6dJWKF6bRUZyQLCZKA1KnJCsQx7nprXjYUFlAouhoVfcKPUTuMiyKSMFvkDOqcoJwP3zz14CFA+nI3OeAHiYvMasoJ/H6xlUj1UXh\ KRZy3cjd581pzxsPKFplBAuUAYacgIpHW+ZuAjGD+KJzQ6N7TFuWUZxXktsIL2mOhvdRWR0Le5pbgBSgkXAOyLUGa66AEZDk42+W7MomNYaDDsxfYHg3LzW\ sVyhqpFuZQ6hhklG9lJr6OBBuk/+pcJYdHuYEuLnJhPeKqF/9xgMOU0e0xLMtkQW+IfDMlm0oAVavHrxk7A4T5L9+yjuxNjN16k2Rqiw== +""" + // printf "The true sign of intelligence is not knowledge but imagination." | openssl rsautl -encrypt -oaep -pubin -inkey bob.pub.pem -out >(base64) + let cipherTextWithBobKeyBase64OAEP = """ +NbydryFPK1o8NBzEdllwnJYCokNY5O9rXvM0cSNVKdmrkDk/Uz4NsCY737QdxDsPTCllQct+w+vZEkhgxN6bfwZJqvgj4R8sqSVMRJTsQeQuEUuAShQu5bG\ +z6TFibK1hr3x6fiS4rhX5KX+e+ByCiEo/xcE5xM9CeBM1dbagJsNZtSNquqyaWQwlXD16HsUTWcUX3urfO3JjPCr5lQnDlXPn8EJDY+UMELDrm3fVk6anOV\ +Fr1AEs3Xe3hoWWZx6U0aregC2kZJvJJh4JZAyxJFb/+/lTtUIq5xr8RqZ+KXao8blz9puqYSUGNZ2uXVGauSEz/fn4oi2mG/q2vk0zgA== """ - let defaultDecryptionError = RSAError.decryptingFailed(description: "The operation couldn’t be completed. (OSStatus error -50 - RSAdecrypt wrong input (err -1))") + let defaultDecryptionError_RSA1_5 = RSAError.decryptingFailed(description: "The operation couldn’t be completed. (OSStatus error -50 - RSAdecrypt wrong input (err -1))") + let defaultDecryptionError_RSAOAEP = RSAError.decryptingFailed(description: "The operation couldn’t be completed. (OSStatus error -50 - RSAdecrypt wrong input (err 26))") override func setUp() { super.setUp() @@ -53,7 +66,7 @@ sVyhqpFuZQ6hhklG9lJr6OBBuk/+pcJYdHuYEuLnJhPeKqF/9xgMOU0e0xLMtkQW+IfDMlm0oAVavHrx super.tearDown() } - func testDecryptingWithAliceKey() { + func testDecryptingWithAliceKey_RSA1_5() { guard privateKeyAlice2048 != nil else { XCTFail() return @@ -66,7 +79,20 @@ sVyhqpFuZQ6hhklG9lJr6OBBuk/+pcJYdHuYEuLnJhPeKqF/9xgMOU0e0xLMtkQW+IfDMlm0oAVavHrx XCTAssertEqual(decryptedMessage, message) } - func testDecryptingWithBobKey() { + func testDecryptingWithAliceKey_RSAOAEP() { + guard privateKeyAlice2048 != nil else { + XCTFail() + return + } + + let decrypter = RSADecrypter(algorithm: .RSAOAEP, privateKey: privateKeyAlice2048!) + let decryptedData = try! decrypter.decrypt(Data(base64Encoded: cipherTextWithAliceKeyBase64OAEP)!) + let decryptedMessage = String(data: decryptedData, encoding: String.Encoding.utf8) + + XCTAssertEqual(decryptedMessage, message) + } + + func testDecryptingWithBobKey_RSA1_5() { guard privateKeyBob2048 != nil else { XCTFail() return @@ -79,7 +105,20 @@ sVyhqpFuZQ6hhklG9lJr6OBBuk/+pcJYdHuYEuLnJhPeKqF/9xgMOU0e0xLMtkQW+IfDMlm0oAVavHrx XCTAssertEqual(decryptedMessage, message) } - func testDecryptingAliceSecretWithBobKey() { + func testDecryptingWithBobKey_RSAOAEP() { + guard privateKeyBob2048 != nil else { + XCTFail() + return + } + + let decrypter = RSADecrypter(algorithm: .RSAOAEP, privateKey: privateKeyBob2048!) + let decryptedData = try! decrypter.decrypt(Data(base64URLEncoded: cipherTextWithBobKeyBase64OAEP)!) + let decryptedMessage = String(data: decryptedData, encoding: String.Encoding.utf8) + + XCTAssertEqual(decryptedMessage, message) + } + + func testDecryptingAliceSecretWithBobKey_RSA1_5() { guard privateKeyBob2048 != nil else { XCTFail() return @@ -89,11 +128,25 @@ sVyhqpFuZQ6hhklG9lJr6OBBuk/+pcJYdHuYEuLnJhPeKqF/9xgMOU0e0xLMtkQW+IfDMlm0oAVavHrx // Decrypting with the wrong key should throw an error XCTAssertThrowsError(try decrypter.decrypt(Data(base64URLEncoded: cipherTextWithAliceKeyBase64)!)) { (error: Error) in - XCTAssertEqual(error as! RSAError, defaultDecryptionError) + XCTAssertEqual(error as! RSAError, defaultDecryptionError_RSA1_5) } } - func testDecryptingBobSecretWithAliceKey() { + func testDecryptingAliceSecretWithBobKey_RSAOAEP() { + guard privateKeyBob2048 != nil else { + XCTFail() + return + } + + let decrypter = RSADecrypter(algorithm: .RSAOAEP, privateKey: privateKeyBob2048!) + + // Decrypting with the wrong key should throw an error + XCTAssertThrowsError(try decrypter.decrypt(Data(base64URLEncoded: cipherTextWithAliceKeyBase64OAEP)!)) { (error: Error) in + XCTAssertEqual(error as! RSAError, defaultDecryptionError_RSAOAEP) + } + } + + func testDecryptingBobSecretWithAliceKey_RSA1_5() { guard privateKeyAlice2048 != nil else { XCTFail() return @@ -103,11 +156,25 @@ sVyhqpFuZQ6hhklG9lJr6OBBuk/+pcJYdHuYEuLnJhPeKqF/9xgMOU0e0xLMtkQW+IfDMlm0oAVavHrx // Decrypting with the wrong key should throw an error XCTAssertThrowsError(try decrypter.decrypt(Data(base64URLEncoded: cipherTextWithBobKeyBase64)!)) { (error: Error) in - XCTAssertEqual(error as! RSAError, defaultDecryptionError) + XCTAssertEqual(error as! RSAError, defaultDecryptionError_RSA1_5) + } + } + + func testDecryptingBobSecretWithAliceKey_RSAOAEP() { + guard privateKeyAlice2048 != nil else { + XCTFail() + return + } + + let decrypter = RSADecrypter(algorithm: .RSAOAEP, privateKey: privateKeyAlice2048!) + + // Decrypting with the wrong key should throw an error + XCTAssertThrowsError(try decrypter.decrypt(Data(base64URLEncoded: cipherTextWithBobKeyBase64OAEP)!)) { (error: Error) in + XCTAssertEqual(error as! RSAError, defaultDecryptionError_RSAOAEP) } } - func testCipherTextLengthTooLong() { + func testCipherTextLengthTooLong_RSA1_5() { guard privateKeyAlice2048 != nil else { XCTFail() return @@ -119,7 +186,19 @@ sVyhqpFuZQ6hhklG9lJr6OBBuk/+pcJYdHuYEuLnJhPeKqF/9xgMOU0e0xLMtkQW+IfDMlm0oAVavHrx } } - func testCipherTextLengthZero() { + func testCipherTextLengthTooLong_RSAOAEP() { + guard privateKeyAlice2048 != nil else { + XCTFail() + return + } + + let decrypter = RSADecrypter(algorithm: .RSAOAEP, privateKey: privateKeyAlice2048!) + XCTAssertThrowsError(try decrypter.decrypt(Data(count: 300))) { (error: Error) in + XCTAssertEqual(error as? RSAError, RSAError.cipherTextLenghtNotSatisfied) + } + } + + func testCipherTextLengthZero_RSA1_5() { guard privateKeyAlice2048 != nil else { XCTFail() return @@ -131,7 +210,19 @@ sVyhqpFuZQ6hhklG9lJr6OBBuk/+pcJYdHuYEuLnJhPeKqF/9xgMOU0e0xLMtkQW+IfDMlm0oAVavHrx } } - func testCipherTextLengthExactlyRight() { + func testCipherTextLengthZero_RSAOAEP() { + guard privateKeyAlice2048 != nil else { + XCTFail() + return + } + + let decrypter = RSADecrypter(algorithm: .RSAOAEP, privateKey: privateKeyAlice2048!) + XCTAssertThrowsError(try decrypter.decrypt(Data(count: 0))) { (error: Error) in + XCTAssertEqual(error as? RSAError, RSAError.cipherTextLenghtNotSatisfied) + } + } + + func testCipherTextLengthExactlyRight_RSA1_5() { guard privateKeyAlice2048 != nil else { XCTFail() return @@ -153,7 +244,30 @@ sVyhqpFuZQ6hhklG9lJr6OBBuk/+pcJYdHuYEuLnJhPeKqF/9xgMOU0e0xLMtkQW+IfDMlm0oAVavHrx } } - func testCipherTextLengthTooLongByOneByte() { + func testCipherTextLengthExactlyRight_RSAOAEP() { + guard privateKeyAlice2048 != nil else { + XCTFail() + return + } + + // For detailed information about the allowed cipher length for RSAOAEP, + // please refer to the RFC(https://tools.ietf.org/html/rfc3447#section-7.1.2 + // https://tools.ietf.org/html/rfc3174#section-1, + // and https://www.rfc-editor.org/errata_search.php?rfc=3447) + // C: ciphertext to be decrypted, an octet string of length k, where k >= 2hLen + 2 + let cipherTextLengthInBytes = SecKeyGetBlockSize(privateKeyAlice2048!) + let testMessage = Data(count: cipherTextLengthInBytes) + + let decrypter = RSADecrypter(algorithm: .RSA1_5, privateKey: privateKeyAlice2048!) + + XCTAssertThrowsError(try decrypter.decrypt(testMessage)) { (error: Error) in + // Should throw "decryption failed", but + // should _not_ throw cipherTextLenghtNotSatisfied + XCTAssertNotEqual(error as? RSAError, RSAError.cipherTextLenghtNotSatisfied) + } + } + + func testCipherTextLengthTooLongByOneByte_RSA1_5() { guard privateKeyAlice2048 != nil else { XCTFail() return @@ -168,7 +282,22 @@ sVyhqpFuZQ6hhklG9lJr6OBBuk/+pcJYdHuYEuLnJhPeKqF/9xgMOU0e0xLMtkQW+IfDMlm0oAVavHrx } } - func testCipherTextLengthTooShortByOneByte() { + func testCipherTextLengthTooLongByOneByte_RSAOAEP() { + guard privateKeyAlice2048 != nil else { + XCTFail() + return + } + + let cipherTextLengthInBytes = SecKeyGetBlockSize(privateKeyAlice2048!) + let testMessage = Data(count: cipherTextLengthInBytes + 1) + + let decrypter = RSADecrypter(algorithm: .RSAOAEP, privateKey: privateKeyAlice2048!) + XCTAssertThrowsError(try decrypter.decrypt(testMessage)) { (error: Error) in + XCTAssertEqual(error as? RSAError, RSAError.cipherTextLenghtNotSatisfied) + } + } + + func testCipherTextLengthTooShortByOneByte_RSA1_5() { guard privateKeyAlice2048 != nil else { XCTFail() return @@ -183,4 +312,19 @@ sVyhqpFuZQ6hhklG9lJr6OBBuk/+pcJYdHuYEuLnJhPeKqF/9xgMOU0e0xLMtkQW+IfDMlm0oAVavHrx } } + func testCipherTextLengthTooShortByOneByte_RSAOAEP() { + guard privateKeyAlice2048 != nil else { + XCTFail() + return + } + + let cipherTextLengthInBytes = SecKeyGetBlockSize(privateKeyAlice2048!) + let testMessage = Data(count: cipherTextLengthInBytes - 1) + + let decrypter = RSADecrypter(algorithm: .RSAOAEP, privateKey: privateKeyAlice2048!) + XCTAssertThrowsError(try decrypter.decrypt(testMessage)) { (error: Error) in + XCTAssertEqual(error as? RSAError, RSAError.cipherTextLenghtNotSatisfied) + } + } + } diff --git a/Tests/RSAEncrypterTests.swift b/Tests/RSAEncrypterTests.swift index 30b90ec0..f08f4015 100644 --- a/Tests/RSAEncrypterTests.swift +++ b/Tests/RSAEncrypterTests.swift @@ -49,7 +49,7 @@ class RSAEncrypterTests: CryptoTestCase { super.tearDown() } - func testEncryptingWithAliceKey() { + func testEncryptingWithAliceKey_RSA1_5() { guard publicKeyAlice2048 != nil, privateKeyAlice2048 != nil else { XCTFail() return @@ -70,7 +70,28 @@ class RSAEncrypterTests: CryptoTestCase { XCTAssertEqual(String(data: plainTextData as Data, encoding: .utf8), message) } - func testEncryptingTwiceWithAliceKey() { + func testEncryptingWithAliceKey_RSAOAEP() { + guard publicKeyAlice2048 != nil, privateKeyAlice2048 != nil else { + XCTFail() + return + } + + let encrypter = RSAEncrypter(algorithm: .RSAOAEP, publicKey: publicKeyAlice2048!) + guard let cipherText = try? encrypter.encrypt(message.data(using: .utf8)!) else { + XCTFail() + return + } + + var decryptionError: Unmanaged? + guard let plainTextData = SecKeyCreateDecryptedData(privateKeyAlice2048!, .rsaEncryptionOAEPSHA1, cipherText as CFData, &decryptionError) else { + XCTFail() + return + } + + XCTAssertEqual(String(data: plainTextData as Data, encoding: .utf8), message) + } + + func testEncryptingTwiceWithAliceKey_RSA1_5() { guard publicKeyAlice2048 != nil, privateKeyAlice2048 != nil else { XCTFail() return @@ -91,7 +112,28 @@ class RSAEncrypterTests: CryptoTestCase { XCTAssertNotEqual(cipherText, cipherText2) } - func testEncryptingWithAliceAndBobKey() { + func testEncryptingTwiceWithAliceKey_RSAOAEP() { + guard publicKeyAlice2048 != nil, privateKeyAlice2048 != nil else { + XCTFail() + return + } + + let encrypter = RSAEncrypter(algorithm: .RSAOAEP, publicKey: publicKeyAlice2048!) + guard let cipherText = try? encrypter.encrypt(message.data(using: .utf8)!) else { + XCTFail() + return + } + + guard let cipherText2 = try? encrypter.encrypt(message.data(using: .utf8)!) else { + XCTFail() + return + } + + // Cipher texts differ because of random padding, see https://en.wikipedia.org/wiki/RSA_(cryptosystem)#Padding_schemes + XCTAssertNotEqual(cipherText, cipherText2) + } + + func testEncryptingWithAliceAndBobKey_RSA1_5() { guard publicKeyAlice2048 != nil, privateKeyAlice2048 != nil, publicKeyBob2048 != nil, privateKeyBob2048 != nil else { XCTFail() return @@ -113,7 +155,29 @@ class RSAEncrypterTests: CryptoTestCase { XCTAssertNotEqual(cipherTextAlice, cipherTextBob) } - func testEncryptingWithBobKey() { + func testEncryptingWithAliceAndBobKey_RSAOAEP() { + guard publicKeyAlice2048 != nil, privateKeyAlice2048 != nil, publicKeyBob2048 != nil, privateKeyBob2048 != nil else { + XCTFail() + return + } + + let encrypterAlice = RSAEncrypter(algorithm: .RSAOAEP, publicKey: publicKeyAlice2048!) + let encrypterBob = RSAEncrypter(algorithm: .RSAOAEP, publicKey: publicKeyBob2048!) + + guard let cipherTextAlice = try? encrypterAlice.encrypt(message.data(using: .utf8)!) else { + XCTFail() + return + } + guard let cipherTextBob = try? encrypterBob.encrypt(message.data(using: .utf8)!) else { + XCTFail() + return + } + + // Cipher texts have to differ (different keys) + XCTAssertNotEqual(cipherTextAlice, cipherTextBob) + } + + func testEncryptingWithBobKey_RSA1_5() { guard publicKeyBob2048 != nil, privateKeyBob2048 != nil else { XCTFail() return @@ -134,7 +198,28 @@ class RSAEncrypterTests: CryptoTestCase { XCTAssertEqual(String(data: plainTextData as Data, encoding: .utf8), message) } - func testPlainTextTooLong() { + func testEncryptingWithBobKey_RSAOAEP() { + guard publicKeyBob2048 != nil, privateKeyBob2048 != nil else { + XCTFail() + return + } + + let encrypter = RSAEncrypter(algorithm: .RSAOAEP, publicKey: publicKeyBob2048!) + guard let cipherText = try? encrypter.encrypt(message.data(using: .utf8)!) else { + XCTFail() + return + } + + var decryptionError: Unmanaged? + guard let plainTextData = SecKeyCreateDecryptedData(privateKeyBob2048!, .rsaEncryptionOAEPSHA1, cipherText as CFData, &decryptionError) else { + XCTFail() + return + } + + XCTAssertEqual(String(data: plainTextData as Data, encoding: .utf8), message) + } + + func testPlainTextTooLong_RSA1_5() { guard publicKeyAlice2048 != nil else { XCTFail() return @@ -146,7 +231,19 @@ class RSAEncrypterTests: CryptoTestCase { } } - func testMaximumPlainTextLength() { + func testPlainTextTooLong_RSAOAEP() { + guard publicKeyAlice2048 != nil else { + XCTFail() + return + } + + let encrypter = RSAEncrypter(algorithm: .RSAOAEP, publicKey: publicKeyAlice2048!) + XCTAssertThrowsError(try encrypter.encrypt(Data(count:300))) { (error: Error) in + XCTAssertEqual(error as? RSAError, RSAError.plainTextLengthNotSatisfied) + } + } + + func testMaximumPlainTextLength_RSA1_5() { guard publicKeyAlice2048 != nil else { XCTFail() return @@ -161,7 +258,23 @@ class RSAEncrypterTests: CryptoTestCase { XCTAssertNoThrow(try encrypter.encrypt(testMessage)) } - func testMaximumPlainTextLengthPlusOne() { + func testMaximumPlainTextLength_RSAOAEP() { + guard publicKeyAlice2048 != nil else { + XCTFail() + return + } + + // RSAES-OAEP can operate on messages of length up to k - 2 * hLen - 2 octets + // (k = octet length of the RSA modulus and hLen = input limitation for the hash function, 20 octets for SHA-1) + // See https://tools.ietf.org/html/rfc3447#section-7.1.1 and https://tools.ietf.org/html/rfc3174#section-1 + let maxMessageLengthInBytes = SecKeyGetBlockSize(publicKeyAlice2048!) - 2 * 20 - 2 + let testMessage = Data(count: maxMessageLengthInBytes) + + let encrypter = RSAEncrypter(algorithm: .RSAOAEP, publicKey: publicKeyAlice2048!) + XCTAssertNoThrow(try encrypter.encrypt(testMessage)) + } + + func testMaximumPlainTextLengthPlusOne_RSA1_5() { guard publicKeyAlice2048 != nil else { XCTFail() return @@ -176,4 +289,19 @@ class RSAEncrypterTests: CryptoTestCase { } } + func testMaximumPlainTextLengthPlusOne_RSAOAEP() { + guard publicKeyAlice2048 != nil else { + XCTFail() + return + } + + let maxMessageLengthInBytes = SecKeyGetBlockSize(publicKeyAlice2048!) - 2 * 20 - 2 + let testMessage = Data(count: maxMessageLengthInBytes + 1) + + let encrypter = RSAEncrypter(algorithm: .RSAOAEP, publicKey: publicKeyAlice2048!) + XCTAssertThrowsError(try encrypter.encrypt(testMessage)) { (error: Error) in + XCTAssertEqual(error as? RSAError, RSAError.plainTextLengthNotSatisfied) + } + } + }