Skip to content

Commit

Permalink
WIP: Add RSAES-OAEP support (#95)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
garrefa authored and Daniel committed Feb 10, 2019
1 parent eb339b2 commit c4d3253
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 38 deletions.
2 changes: 2 additions & 0 deletions JOSESwift/Sources/Algorithms.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}

Expand Down
30 changes: 25 additions & 5 deletions JOSESwift/Sources/CryptoImplementation/RSA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,29 +50,49 @@ fileprivate extension AsymmetricKeyAlgorithm {
switch self {
case .RSA1_5:
return .rsaEncryptionPKCS1
default:
case .RSAOAEP:
return .rsaEncryptionOAEPSHA1
case .direct:
return nil
}
}

/// 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
}
}

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
}
}
Expand Down
22 changes: 11 additions & 11 deletions JOSESwift/Sources/Decrypter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public struct Decrypter {
/// - Returns: A fully initialized `Decrypter` or `nil` if provided key is of the wrong type.
public init?<KeyType>(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
}
Expand All @@ -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
Expand Down Expand Up @@ -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
}
}

Expand Down
6 changes: 3 additions & 3 deletions JOSESwift/Sources/Encrypter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public struct Encrypter<KeyType> {
/// - 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
}
Expand All @@ -90,12 +90,12 @@ public struct Encrypter<KeyType> {
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.
Expand Down
168 changes: 156 additions & 12 deletions Tests/RSADecrypterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,29 @@ 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)
let cipherTextWithBobKeyBase64 = """
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()
Expand All @@ -53,7 +66,7 @@ sVyhqpFuZQ6hhklG9lJr6OBBuk/+pcJYdHuYEuLnJhPeKqF/9xgMOU0e0xLMtkQW+IfDMlm0oAVavHrx
super.tearDown()
}

func testDecryptingWithAliceKey() {
func testDecryptingWithAliceKey_RSA1_5() {
guard privateKeyAlice2048 != nil else {
XCTFail()
return
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
}
}

}
Loading

0 comments on commit c4d3253

Please sign in to comment.