diff --git a/.gitignore b/.gitignore index f2d3b12d..94252e39 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,25 @@ + +# Created by https://www.gitignore.io/api/xcode,macos,carthage,cocoapods + +### Carthage ### +# Carthage # -# macOS -# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build +### CocoaPods ### +## CocoaPods GitIgnore Template + +# CocoaPods - Only use to conserve bandwidth / Save time on Pushing +# - Also handy if you have a large number of dependant pods +# - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE +Pods/ + +### macOS ### # General -*.DS_Store +.DS_Store .AppleDouble .LSOverride @@ -29,15 +45,22 @@ Network Trash Folder Temporary Items .apdisk -# +### Xcode ### # Xcode # +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore -## Build generated +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) build/ DerivedData/ - -## Various settings +*.moved-aside *.pbxuser !default.pbxuser *.mode1v3 @@ -46,58 +69,13 @@ DerivedData/ !default.mode2v3 *.perspectivev3 !default.perspectivev3 -xcuserdata/ -**/xcuserdata/ - -## Other -*.moved-aside -*.xccheckout -*.xcscmblueprint -## Obj-C/Swift specific -*.hmap -*.ipa -*.dSYM.zip -*.dSYM +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno -## Playgrounds -timeline.xctimeline -playground.xcworkspace -# Swift Package Manager -# -# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. -# Packages/ -# Package.pins -.build/ - -# CocoaPods -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control -# -Pods/ - -# Carthage -# -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - -Carthage/Build - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the -# screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/#source-control - -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots -fastlane/test_output -/test_output - -.idea/ -sonar-reports/ +# End of https://www.gitignore.io/api/xcode,macos,carthage,cocoapods diff --git a/JOSESwift.xcodeproj/project.pbxproj b/JOSESwift.xcodeproj/project.pbxproj index d0daff35..30b89790 100644 --- a/JOSESwift.xcodeproj/project.pbxproj +++ b/JOSESwift.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 6505236E1FB4940100E0B1B1 /* AESEncrypter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6505236D1FB4940100E0B1B1 /* AESEncrypter.swift */; }; 650523701FB494BE00E0B1B1 /* AESDecrypter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6505236F1FB494BE00E0B1B1 /* AESDecrypter.swift */; }; + 6506D9E920F4CA2000F34DD8 /* SymmetricKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6506D9E820F4CA2000F34DD8 /* SymmetricKeyTests.swift */; }; 65125A321FBF85FA007CF3AE /* JWSDeserializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65125A311FBF85FA007CF3AE /* JWSDeserializationTests.swift */; }; 6514ADC92031DD15008A4DD3 /* ASN1DEREncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6514ADC82031DD15008A4DD3 /* ASN1DEREncoding.swift */; }; 6514ADCB2031DD27008A4DD3 /* ASN1DEREncodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6514ADCA2031DD27008A4DD3 /* ASN1DEREncodingTests.swift */; }; @@ -16,6 +17,8 @@ 652F6DE91F73E6780002DEE0 /* Serializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 652F6DE81F73E6780002DEE0 /* Serializer.swift */; }; 6533552A1F8F6B6800A660C6 /* JWEHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653355291F8F6B6800A660C6 /* JWEHeader.swift */; }; 6533552E1F8FB61000A660C6 /* JWE.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6533552D1F8FB61000A660C6 /* JWE.swift */; }; + 653365E520ECCB71002630D7 /* JWEDirectEncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653365E420ECCB71002630D7 /* JWEDirectEncryptionTests.swift */; }; + 65344C3E20F4CC9000FCBBA1 /* DataSymmetricKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65344C3D20F4CC9000FCBBA1 /* DataSymmetricKey.swift */; }; 65353F5D1F750A6A003E099B /* DataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65353F5C1F750A6A003E099B /* DataExtensions.swift */; }; 653656072035D6C700A3AC3B /* JWKSetCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653656062035D6C700A3AC3B /* JWKSetCollectionTests.swift */; }; 653656092035D86E00A3AC3B /* JWKSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653656082035D86E00A3AC3B /* JWKSet.swift */; }; @@ -42,10 +45,13 @@ 65A77E941F7285A900A66DDE /* JOSEHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65A77E931F7285A900A66DDE /* JOSEHeader.swift */; }; 65A7A1991F7295F5009449E7 /* Payload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65A7A1981F7295F5009449E7 /* Payload.swift */; }; 65A9D3DC1F45CDD7004E0B61 /* JOSESwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65FBFDE21F45CC7C005C7D68 /* JOSESwift.framework */; }; + 65A9EE4B20FDD7A900E9C566 /* EncrypterDecrypterInitializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65A9EE4A20FDD7A900E9C566 /* EncrypterDecrypterInitializationTests.swift */; }; 65D1D0651F7A4DB3006377CD /* DataConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65D1D0641F7A4DB3006377CD /* DataConvertible.swift */; }; 65D1D0671F7A878D006377CD /* Deserializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65D1D0661F7A878D006377CD /* Deserializer.swift */; }; 65D8680F1F7CE35000769BBF /* RSAVerifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65D8680E1F7CE35000769BBF /* RSAVerifier.swift */; }; 65D868111F7CEBA200769BBF /* Verifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65D868101F7CEBA200769BBF /* Verifier.swift */; }; + 65D8E8E820F499EF0059506A /* SymmetricKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65D8E8E720F499EF0059506A /* SymmetricKeys.swift */; }; + 65D8E8EA20F4AF880059506A /* SymmetricKeyCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65D8E8E920F4AF880059506A /* SymmetricKeyCodable.swift */; }; 65E733CC1FEBE8320009EAC6 /* JWKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E733CB1FEBE8320009EAC6 /* JWKExtensions.swift */; }; 65E733D11FEBF7960009EAC6 /* JWKParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E733D01FEBF7960009EAC6 /* JWKParameters.swift */; }; 65E733D31FEBFDB30009EAC6 /* JWKtoJSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E733D21FEBFDB30009EAC6 /* JWKtoJSONTests.swift */; }; @@ -55,7 +61,7 @@ 65FBFDE71F45CC7C005C7D68 /* JOSESwift.h in Headers */ = {isa = PBXBuildFile; fileRef = 65FBFDE51F45CC7C005C7D68 /* JOSESwift.h */; settings = {ATTRIBUTES = (Public, ); }; }; C803EFE51FA77E3000B71335 /* JWSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C803EFE41FA77E3000B71335 /* JWSTests.swift */; }; C803EFE91FA7893A00B71335 /* JWSHeaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C803EFE81FA7893A00B71335 /* JWSHeaderTests.swift */; }; - C803EFED1FA8849C00B71335 /* JWETests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C803EFEC1FA8849C00B71335 /* JWETests.swift */; }; + C803EFED1FA8849C00B71335 /* JWERSATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C803EFEC1FA8849C00B71335 /* JWERSATests.swift */; }; C803EFEF1FA884C100B71335 /* JWEHeaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C803EFEE1FA884C100B71335 /* JWEHeaderTests.swift */; }; C803EFF31FA8A98F00B71335 /* DataExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C803EFF21FA8A98F00B71335 /* DataExtensionTests.swift */; }; C81DBD581FFE66E700ECF69E /* AES.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81DBD571FFE66E700ECF69E /* AES.swift */; }; @@ -66,7 +72,7 @@ C83070051FD1B7390068C5CB /* AESDecrypterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83070041FD1B7390068C5CB /* AESDecrypterTests.swift */; }; C84BDE171FAB1CB60002B5D0 /* RSASignerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C84BDE161FAB1CB60002B5D0 /* RSASignerTests.swift */; }; C84BDE191FAB44BE0002B5D0 /* CryptoTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C84BDE181FAB44BE0002B5D0 /* CryptoTestCase.swift */; }; - C84BDE1B1FAB461B0002B5D0 /* TestKey.plist in Resources */ = {isa = PBXBuildFile; fileRef = C84BDE1A1FAB461B0002B5D0 /* TestKey.plist */; }; + C84BDE1B1FAB461B0002B5D0 /* TestKeys.plist in Resources */ = {isa = PBXBuildFile; fileRef = C84BDE1A1FAB461B0002B5D0 /* TestKeys.plist */; }; C85012E31FE04E0C00EC49FA /* SecureRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = C85012E21FE04E0C00EC49FA /* SecureRandom.swift */; }; C85B1EF2204D82640026BDCB /* JWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C85B1EF1204D82640026BDCB /* JWS.swift */; }; C85B1EF4204D82860026BDCB /* Signer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C85B1EF3204D82860026BDCB /* Signer.swift */; }; @@ -101,6 +107,7 @@ /* Begin PBXFileReference section */ 6505236D1FB4940100E0B1B1 /* AESEncrypter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AESEncrypter.swift; sourceTree = ""; }; 6505236F1FB494BE00E0B1B1 /* AESDecrypter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AESDecrypter.swift; sourceTree = ""; }; + 6506D9E820F4CA2000F34DD8 /* SymmetricKeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymmetricKeyTests.swift; sourceTree = ""; }; 65125A311FBF85FA007CF3AE /* JWSDeserializationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWSDeserializationTests.swift; sourceTree = ""; }; 6514ADC82031DD15008A4DD3 /* ASN1DEREncoding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASN1DEREncoding.swift; sourceTree = ""; }; 6514ADCA2031DD27008A4DD3 /* ASN1DEREncodingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASN1DEREncodingTests.swift; sourceTree = ""; }; @@ -108,6 +115,8 @@ 652F6DE81F73E6780002DEE0 /* Serializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Serializer.swift; sourceTree = ""; }; 653355291F8F6B6800A660C6 /* JWEHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWEHeader.swift; sourceTree = ""; }; 6533552D1F8FB61000A660C6 /* JWE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWE.swift; sourceTree = ""; }; + 653365E420ECCB71002630D7 /* JWEDirectEncryptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWEDirectEncryptionTests.swift; sourceTree = ""; }; + 65344C3D20F4CC9000FCBBA1 /* DataSymmetricKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSymmetricKey.swift; sourceTree = ""; }; 65353F5C1F750A6A003E099B /* DataExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataExtensions.swift; sourceTree = ""; }; 653656062035D6C700A3AC3B /* JWKSetCollectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWKSetCollectionTests.swift; sourceTree = ""; }; 653656082035D86E00A3AC3B /* JWKSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWKSet.swift; sourceTree = ""; }; @@ -135,10 +144,13 @@ 65A7A1981F7295F5009449E7 /* Payload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Payload.swift; sourceTree = ""; }; 65A9D3D71F45CDD7004E0B61 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 65A9D3DB1F45CDD7004E0B61 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 65A9EE4A20FDD7A900E9C566 /* EncrypterDecrypterInitializationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncrypterDecrypterInitializationTests.swift; sourceTree = ""; }; 65D1D0641F7A4DB3006377CD /* DataConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataConvertible.swift; sourceTree = ""; }; 65D1D0661F7A878D006377CD /* Deserializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deserializer.swift; sourceTree = ""; }; 65D8680E1F7CE35000769BBF /* RSAVerifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSAVerifier.swift; sourceTree = ""; }; 65D868101F7CEBA200769BBF /* Verifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Verifier.swift; sourceTree = ""; }; + 65D8E8E720F499EF0059506A /* SymmetricKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymmetricKeys.swift; sourceTree = ""; }; + 65D8E8E920F4AF880059506A /* SymmetricKeyCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymmetricKeyCodable.swift; sourceTree = ""; }; 65E733CB1FEBE8320009EAC6 /* JWKExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWKExtensions.swift; sourceTree = ""; }; 65E733D01FEBF7960009EAC6 /* JWKParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWKParameters.swift; sourceTree = ""; }; 65E733D21FEBFDB30009EAC6 /* JWKtoJSONTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWKtoJSONTests.swift; sourceTree = ""; }; @@ -150,7 +162,7 @@ 65FBFDE61F45CC7C005C7D68 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C803EFE41FA77E3000B71335 /* JWSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWSTests.swift; sourceTree = ""; }; C803EFE81FA7893A00B71335 /* JWSHeaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWSHeaderTests.swift; sourceTree = ""; }; - C803EFEC1FA8849C00B71335 /* JWETests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWETests.swift; sourceTree = ""; }; + C803EFEC1FA8849C00B71335 /* JWERSATests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWERSATests.swift; sourceTree = ""; }; C803EFEE1FA884C100B71335 /* JWEHeaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWEHeaderTests.swift; sourceTree = ""; }; C803EFF21FA8A98F00B71335 /* DataExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataExtensionTests.swift; sourceTree = ""; }; C81DBD571FFE66E700ECF69E /* AES.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AES.swift; sourceTree = ""; }; @@ -159,7 +171,7 @@ C83070041FD1B7390068C5CB /* AESDecrypterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AESDecrypterTests.swift; sourceTree = ""; }; C84BDE161FAB1CB60002B5D0 /* RSASignerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSASignerTests.swift; sourceTree = ""; }; C84BDE181FAB44BE0002B5D0 /* CryptoTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoTestCase.swift; sourceTree = ""; }; - C84BDE1A1FAB461B0002B5D0 /* TestKey.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = TestKey.plist; sourceTree = ""; }; + C84BDE1A1FAB461B0002B5D0 /* TestKeys.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = TestKeys.plist; sourceTree = ""; }; C85012E21FE04E0C00EC49FA /* SecureRandom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureRandom.swift; sourceTree = ""; }; C85B1EF1204D82640026BDCB /* JWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JWS.swift; sourceTree = ""; }; C85B1EF3204D82860026BDCB /* Signer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Signer.swift; sourceTree = ""; }; @@ -221,8 +233,10 @@ isa = PBXGroup; children = ( 65676D8A1FC220C70031B26D /* JWEDeserializationTests.swift */, - C803EFEC1FA8849C00B71335 /* JWETests.swift */, + C803EFEC1FA8849C00B71335 /* JWERSATests.swift */, C803EFEE1FA884C100B71335 /* JWEHeaderTests.swift */, + 653365E420ECCB71002630D7 /* JWEDirectEncryptionTests.swift */, + 65A9EE4A20FDD7A900E9C566 /* EncrypterDecrypterInitializationTests.swift */, ); name = JWE; sourceTree = ""; @@ -284,7 +298,7 @@ 65125A2E1FBF6B9E007CF3AE /* JWE */, 65125A2C1FBF6B79007CF3AE /* JWS */, 65E733CD1FEBE9670009EAC6 /* JWK */, - C84BDE1A1FAB461B0002B5D0 /* TestKey.plist */, + C84BDE1A1FAB461B0002B5D0 /* TestKeys.plist */, 65A9D3DB1F45CDD7004E0B61 /* Info.plist */, ); path = Tests; @@ -327,6 +341,7 @@ 65684A4E2031971A00E56C68 /* RSAPublicKeyToSecKeyTests.swift */, 653656062035D6C700A3AC3B /* JWKSetCollectionTests.swift */, 6536560A2035DC3900A3AC3B /* JWKSetCodingTests.swift */, + 6506D9E820F4CA2000F34DD8 /* SymmetricKeyTests.swift */, ); name = JWK; sourceTree = ""; @@ -340,6 +355,8 @@ 653656082035D86E00A3AC3B /* JWKSet.swift */, 6536560C2035DF8300A3AC3B /* JWKSetCodable.swift */, 65F44EB21FE2E1C6000C5EA0 /* RSAKeys.swift */, + 65D8E8E720F499EF0059506A /* SymmetricKeys.swift */, + 65D8E8E920F4AF880059506A /* SymmetricKeyCodable.swift */, 65826AB12028696000AFFC46 /* RSAKeyCodable.swift */, 6582614E2029F2D100B594ED /* ASN1DERParsing.swift */, 6514ADC82031DD15008A4DD3 /* ASN1DEREncoding.swift */, @@ -423,6 +440,7 @@ isa = PBXGroup; children = ( 6582614C2029E98A00B594ED /* DataRSAPublicKey.swift */, + 65344C3D20F4CC9000FCBBA1 /* DataSymmetricKey.swift */, 6546FB0E2029DD10002E421F /* SecKeyRSAPublicKey.swift */, C85012E21FE04E0C00EC49FA /* SecureRandom.swift */, C81DD9271FD7096100026024 /* HMAC.swift */, @@ -560,7 +578,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - C84BDE1B1FAB461B0002B5D0 /* TestKey.plist in Resources */, + C84BDE1B1FAB461B0002B5D0 /* TestKeys.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -595,6 +613,9 @@ 6536560B2035DC3900A3AC3B /* JWKSetCodingTests.swift in Sources */, 65676D8B1FC220C70031B26D /* JWEDeserializationTests.swift in Sources */, 6546D606203580C6007217FB /* JWKRSADecodingTests.swift in Sources */, + 65A9EE4B20FDD7A900E9C566 /* EncrypterDecrypterInitializationTests.swift in Sources */, + 653365E520ECCB71002630D7 /* JWEDirectEncryptionTests.swift in Sources */, + 6506D9E920F4CA2000F34DD8 /* SymmetricKeyTests.swift in Sources */, 65684A4D2031935200E56C68 /* RSAPublicKeyToDataTests.swift in Sources */, 6575696D203EF9CE004A0EFD /* JWSValidationTests.swift in Sources */, 65A103A1202B03BB00D22BF5 /* ASN1DERParsingTests.swift in Sources */, @@ -606,7 +627,7 @@ C803EFE51FA77E3000B71335 /* JWSTests.swift in Sources */, C84BDE191FAB44BE0002B5D0 /* CryptoTestCase.swift in Sources */, 6514ADCB2031DD27008A4DD3 /* ASN1DEREncodingTests.swift in Sources */, - C803EFED1FA8849C00B71335 /* JWETests.swift in Sources */, + C803EFED1FA8849C00B71335 /* JWERSATests.swift in Sources */, C8F096501FC56B25000BEE4D /* RSAEncrypterTests.swift in Sources */, 65125A321FBF85FA007CF3AE /* JWSDeserializationTests.swift in Sources */, 65A103A3202B0CDF00D22BF5 /* DataRSAPublicKeyTests.swift in Sources */, @@ -646,10 +667,13 @@ 65826AB22028696000AFFC46 /* RSAKeyCodable.swift in Sources */, C8610F092029B15600859FCC /* Algorithms.swift in Sources */, 6505236E1FB4940100E0B1B1 /* AESEncrypter.swift in Sources */, + 65D8E8E820F499EF0059506A /* SymmetricKeys.swift in Sources */, 65D1D0651F7A4DB3006377CD /* DataConvertible.swift in Sources */, 6533552E1F8FB61000A660C6 /* JWE.swift in Sources */, C85B1EF4204D82860026BDCB /* Signer.swift in Sources */, + 65D8E8EA20F4AF880059506A /* SymmetricKeyCodable.swift in Sources */, C85B1EF6204D82970026BDCB /* RSASigner.swift in Sources */, + 65344C3E20F4CC9000FCBBA1 /* DataSymmetricKey.swift in Sources */, 65D868111F7CEBA200769BBF /* Verifier.swift in Sources */, C86B876D203D857B00208387 /* JOSESwiftError.swift in Sources */, 65A77E941F7285A900A66DDE /* JOSEHeader.swift in Sources */, diff --git a/JOSESwift.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/JOSESwift.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/JOSESwift.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/JOSESwift/Sources/AESDecrypter.swift b/JOSESwift/Sources/AESDecrypter.swift index 0e835e19..b3d6d4b1 100644 --- a/JOSESwift/Sources/AESDecrypter.swift +++ b/JOSESwift/Sources/AESDecrypter.swift @@ -25,7 +25,15 @@ import Foundation /// A `SymmetricDecrypter` to decrypt a cipher text with an `AES` algorithm. internal struct AESDecrypter: SymmetricDecrypter { + typealias KeyType = AES.KeyType + let algorithm: SymmetricKeyAlgorithm + let symmetricKey: KeyType? + + init(algorithm: SymmetricKeyAlgorithm, symmetricKey: KeyType? = nil) { + self.algorithm = algorithm + self.symmetricKey = symmetricKey + } func decrypt(_ context: SymmetricDecryptionContext, with symmetricKey: Data) throws -> Data { // Check if the key length contains both HMAC key and the actual symmetric key. diff --git a/JOSESwift/Sources/AESEncrypter.swift b/JOSESwift/Sources/AESEncrypter.swift index e2974a3a..4cb3c648 100644 --- a/JOSESwift/Sources/AESEncrypter.swift +++ b/JOSESwift/Sources/AESEncrypter.swift @@ -25,7 +25,15 @@ import Foundation /// A `SymmetricEncrypter` to encrypt plaintext with an `AES` algorithm. internal struct AESEncrypter: SymmetricEncrypter { + typealias KeyType = AES.KeyType + let algorithm: SymmetricKeyAlgorithm + let symmetricKey: KeyType? + + init(algorithm: SymmetricKeyAlgorithm, symmetricKey: KeyType? = nil) { + self.algorithm = algorithm + self.symmetricKey = symmetricKey + } func encrypt(_ plaintext: Data, with symmetricKey: Data, additionalAuthenticatedData: Data) throws -> SymmetricEncryptionContext { // Generate random intitialization vector. diff --git a/JOSESwift/Sources/Algorithms.swift b/JOSESwift/Sources/Algorithms.swift index aa737be3..c18042a4 100644 --- a/JOSESwift/Sources/Algorithms.swift +++ b/JOSESwift/Sources/Algorithms.swift @@ -34,8 +34,10 @@ 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) +/// - 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 direct = "dir" } /// An algorithm for symmetric encryption and decryption. diff --git a/JOSESwift/Sources/CryptoImplementation/AES.swift b/JOSESwift/Sources/CryptoImplementation/AES.swift index 14ed0c33..ec9a4874 100644 --- a/JOSESwift/Sources/CryptoImplementation/AES.swift +++ b/JOSESwift/Sources/CryptoImplementation/AES.swift @@ -47,6 +47,8 @@ fileprivate extension SymmetricKeyAlgorithm { } internal struct AES { + typealias KeyType = Data + /// Encrypts a plain text using a given `AES` algorithm, the corresponding symmetric key and an initialization vector. /// /// - Parameters: @@ -56,7 +58,7 @@ internal struct AES { /// - initializationVector: The initial block. /// - Returns: The cipher text (encrypted plain text). /// - Throws: `AESError` if any error occurs during encryption. - static func encrypt(plaintext: Data, with encryptionKey: Data, using algorithm: SymmetricKeyAlgorithm, and initializationVector: Data) throws -> Data { + static func encrypt(plaintext: Data, with encryptionKey: KeyType, using algorithm: SymmetricKeyAlgorithm, and initializationVector: Data) throws -> Data { switch algorithm { case .A256CBCHS512: guard algorithm.checkAESKeyLength(for: encryptionKey) else { diff --git a/JOSESwift/Sources/CryptoImplementation/DataSymmetricKey.swift b/JOSESwift/Sources/CryptoImplementation/DataSymmetricKey.swift new file mode 100644 index 00000000..c856fe75 --- /dev/null +++ b/JOSESwift/Sources/CryptoImplementation/DataSymmetricKey.swift @@ -0,0 +1,34 @@ +// +// DataSymmetricKey.swift +// JOSESwift +// +// Created by Daniel Egger on 10.07.18. +// +// --------------------------------------------------------------------------- +// Copyright 2018 Airside Mobile Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// --------------------------------------------------------------------------- +// + +import Foundation + +extension Data: ExpressibleAsSymmetricKeyComponents { + public static func representing(symmetricKeyComponents components: SymmetricKeyComponents) throws -> Data { + return components + } + + public func symmetricKeyComponents() throws -> SymmetricKeyComponents { + return self + } +} diff --git a/JOSESwift/Sources/CryptoImplementation/RSA.swift b/JOSESwift/Sources/CryptoImplementation/RSA.swift index 817a935b..c699c21e 100644 --- a/JOSESwift/Sources/CryptoImplementation/RSA.swift +++ b/JOSESwift/Sources/CryptoImplementation/RSA.swift @@ -50,6 +50,8 @@ fileprivate extension AsymmetricKeyAlgorithm { switch self { case .RSA1_5: return .rsaEncryptionPKCS1 + default: + return nil } } @@ -60,7 +62,9 @@ fileprivate extension AsymmetricKeyAlgorithm { 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) + return plainText.count <= (SecKeyGetBlockSize(publicKey) - 11) + default: + return false } } @@ -68,6 +72,8 @@ fileprivate extension AsymmetricKeyAlgorithm { switch self { case .RSA1_5: return cipherText.count == SecKeyGetBlockSize(privateKey) + default: + return false } } } diff --git a/JOSESwift/Sources/CryptoImplementation/SecureRandom.swift b/JOSESwift/Sources/CryptoImplementation/SecureRandom.swift index 5f1832a2..18e610ba 100644 --- a/JOSESwift/Sources/CryptoImplementation/SecureRandom.swift +++ b/JOSESwift/Sources/CryptoImplementation/SecureRandom.swift @@ -24,17 +24,17 @@ import Foundation import Security -internal enum SecureRandomError: Error { +public enum SecureRandomError: Error { case failed(status: OSStatus) } -internal struct SecureRandom { +public struct SecureRandom { /// Generates secure random data with a given count. /// /// - Parameter count: The count of the random generated data. /// - Returns: The random generated data. /// - Throws: `SecureRandomError` if any error occurs during generation of secure random bytes. - internal static func generate(count: Int) throws -> Data { + public static func generate(count: Int) throws -> Data { var generatedRandom = Data(count: count) let randomGenerationStatus = generatedRandom.withUnsafeMutableBytes { mutableRandomBytes in diff --git a/JOSESwift/Sources/DataExtensions.swift b/JOSESwift/Sources/DataExtensions.swift index 97b24782..a657ab63 100644 --- a/JOSESwift/Sources/DataExtensions.swift +++ b/JOSESwift/Sources/DataExtensions.swift @@ -28,7 +28,7 @@ extension Data { /// /// - Parameter base64URLString: The base64url encoded string to parse. /// - Returns: `nil` if the input is not recognized as valid base64url. - init?(base64URLEncoded base64URLString: String) { + public init?(base64URLEncoded base64URLString: String) { var s = base64URLString .replacingOccurrences(of: "-", with: "+") .replacingOccurrences(of: "_", with: "/") @@ -48,7 +48,7 @@ extension Data { /// /// - Parameter base64URLData: The base64url, UTF-8 encoded data. /// - Returns: `nil` if the input is not recognized as valid base64url. - init?(base64URLEncoded base64URLData: Data) { + public init?(base64URLEncoded base64URLData: Data) { guard let s = String(data: base64URLData, encoding: .utf8) else { return nil } @@ -59,7 +59,7 @@ extension Data { /// Returns a base64url encoded string. /// /// - Returns: The base64url encoded string. - func base64URLEncodedString() -> String { + public func base64URLEncodedString() -> String { let s = self.base64EncodedString() return s .replacingOccurrences(of: "=", with: "") @@ -70,7 +70,7 @@ extension Data { /// Returns base64url encoded data. /// /// - Returns: The base64url encoded data. - func base64URLEncodedData() -> Data { + public func base64URLEncodedData() -> Data { // UTF-8 can represent [all Unicode characters](https://en.wikipedia.org/wiki/UTF-8), so this // forced unwrap is safe. See also [this](https://stackoverflow.com/a/46152738/5233456) SO answer. return self.base64URLEncodedString().data(using: .utf8)! diff --git a/JOSESwift/Sources/Decrypter.swift b/JOSESwift/Sources/Decrypter.swift index 7d8a9fd2..fc80e153 100644 --- a/JOSESwift/Sources/Decrypter.swift +++ b/JOSESwift/Sources/Decrypter.swift @@ -36,6 +36,7 @@ internal protocol AsymmetricDecrypter { internal protocol SymmetricDecrypter { var algorithm: SymmetricKeyAlgorithm { get } + var symmetricKey: Data? { get } /// Decrypts a cipher text contained in the `SymmetricDecryptionContext` using a given symmetric key. /// @@ -70,22 +71,45 @@ public struct Decrypter { /// /// - Parameters: /// - keyDecryptionAlgorithm: The algorithm used to decrypt the shared content encryption key. - /// - kdk: The private key used to decrypt the shared content encryption key. - /// Currently supported key types are: `SecKey`. + /// - key: The key used to perform the decryption. If the `keyDecryptionAlgorithm` is `.direct`, the + /// `decryptionKey` is the shared symmetric content encryption key. Otherwise the `decryptionKey` is the + /// private key of the receiver. See [RFC-7516](https://tools.ietf.org/html/rfc7516#section-5.2) for + /// details. /// - contentDecryptionAlgorithm: The algorithm used to decrypt the JWE's payload. /// - Returns: A fully initialized `Decrypter` or `nil` if provided key is of the wrong type. - public init?(keyDecryptionAlgorithm: AsymmetricKeyAlgorithm, keyDecryptionKey kdk: KeyType, contentDecryptionAlgorithm: SymmetricKeyAlgorithm) { + public init?(keyDecryptionAlgorithm: AsymmetricKeyAlgorithm, decryptionKey key: KeyType, contentDecryptionAlgorithm: SymmetricKeyAlgorithm) { switch (keyDecryptionAlgorithm, contentDecryptionAlgorithm) { case (.RSA1_5, .A256CBCHS512): - guard type(of: kdk) is RSADecrypter.KeyType.Type else { + guard type(of: key) is RSADecrypter.KeyType.Type else { return nil } // swiftlint:disable:next force_cast - self.asymmetric = RSADecrypter(algorithm: keyDecryptionAlgorithm, privateKey: kdk as! RSADecrypter.KeyType) + self.asymmetric = RSADecrypter(algorithm: keyDecryptionAlgorithm, privateKey: (key as! RSADecrypter.KeyType)) self.symmetric = AESDecrypter(algorithm: contentDecryptionAlgorithm) + case (.direct, .A256CBCHS512): + guard type(of: key) is AESDecrypter.KeyType.Type else { + return nil + } + + self.asymmetric = RSADecrypter(algorithm: keyDecryptionAlgorithm) + // swiftlint:disable:next force_cast + self.symmetric = AESDecrypter(algorithm: contentDecryptionAlgorithm, symmetricKey: (key as! AESDecrypter.KeyType)) } } + /// Constructs a decrypter used to decrypt a JWE. + /// + /// - Parameters: + /// - keyDecryptionAlgorithm: The algorithm used to decrypt the shared content encryption key. + /// - kdk: The private key used to decrypt the shared content encryption key. + /// Currently supported key types are: `SecKey`. + /// - contentDecryptionAlgorithm: The algorithm used to decrypt the JWE's payload. + /// - Returns: A fully initialized `Decrypter` or `nil` if provided key is of the wrong type. + @available(*, deprecated, message: "Use `init?(keyDecryptionAlgorithm:decryptionKey:contentDecyptionAlgorithm:)` instead") + public init?(keyDecryptionAlgorithm: AsymmetricKeyAlgorithm, keyDecryptionKey kdk: KeyType, contentDecryptionAlgorithm: SymmetricKeyAlgorithm) { + self.init(keyDecryptionAlgorithm: keyDecryptionAlgorithm, decryptionKey: kdk, contentDecryptionAlgorithm: contentDecryptionAlgorithm) + } + internal func decrypt(_ context: DecryptionContext) throws -> Data { guard let alg = context.header.algorithm, alg == asymmetric.algorithm else { throw JWEError.keyEncryptionAlgorithmMismatch @@ -96,15 +120,31 @@ public struct Decrypter { } var cek: Data - // 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 + if (alg == .direct) { + guard context.encryptedKey == Data() else { + throw JOSESwiftError.decryptingFailed( + description: "Direct encryption does not expect an encrypted key." + ) + } + guard let symmetricKey = symmetric.symmetricKey else { + throw JOSESwiftError.decryptingFailed( + description: "Did not supply a shared symmetric key for decryption." + ) + } + + cek = symmetricKey } 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 + } } let symmetricContext = SymmetricDecryptionContext( diff --git a/JOSESwift/Sources/Encrypter.swift b/JOSESwift/Sources/Encrypter.swift index 55d55a45..089b6d5c 100644 --- a/JOSESwift/Sources/Encrypter.swift +++ b/JOSESwift/Sources/Encrypter.swift @@ -38,6 +38,7 @@ internal protocol AsymmetricEncrypter { internal protocol SymmetricEncrypter { /// The algorithm used to encrypt plaintext. var algorithm: SymmetricKeyAlgorithm { get } + var symmetricKey: Data? { get } /// Encrypts a plain text using the corresponding symmetric key and additional authenticated data. /// @@ -71,22 +72,45 @@ public struct Encrypter { /// /// - Parameters: /// - keyEncryptionAlgorithm: The algorithm used to encrypt the shared content encryption key. - /// - kek: The public key of the receiver used to encrypt the shared content encryption key. - /// Currently supported key types are: `SecKey`. + /// - key: The key used to perform the encryption. If the `keyEncryptionAlgorithm` is `.direct`, the + /// `encryptionKey` is the shared symmetric content encryption key. Otherwise the `encryptionKey` is the + /// public key of the receiver. See [RFC-7516](https://tools.ietf.org/html/rfc7516#section-5.1) for + /// details. /// - contentEncyptionAlgorithm: The algorithm used to encrypt the JWE's payload. /// - Returns: A fully initialized `Encrypter` or `nil` if provided key is of the wrong type. - public init?(keyEncryptionAlgorithm: AsymmetricKeyAlgorithm, keyEncryptionKey kek: KeyType, contentEncyptionAlgorithm: SymmetricKeyAlgorithm) { + public init?(keyEncryptionAlgorithm: AsymmetricKeyAlgorithm, encryptionKey key: KeyType, contentEncyptionAlgorithm: SymmetricKeyAlgorithm) { switch (keyEncryptionAlgorithm, contentEncyptionAlgorithm) { - case (.RSA1_5, .A256CBCHS512) : - guard type(of: kek) is RSAEncrypter.KeyType.Type else { + case (.RSA1_5, .A256CBCHS512): + guard type(of: key) is RSAEncrypter.KeyType.Type else { return nil } // swiftlint:disable:next force_cast - self.asymmetric = RSAEncrypter(algorithm: keyEncryptionAlgorithm, publicKey: kek as! RSAEncrypter.KeyType) + self.asymmetric = RSAEncrypter(algorithm: keyEncryptionAlgorithm, publicKey: (key as! RSAEncrypter.KeyType)) self.symmetric = AESEncrypter(algorithm: contentEncyptionAlgorithm) + 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. + /// + /// - Parameters: + /// - keyEncryptionAlgorithm: The algorithm used to encrypt the shared content encryption key. + /// - kek: The public key of the receiver used to encrypt the shared content encryption key. + /// Currently supported key types are: `SecKey`. + /// - contentEncyptionAlgorithm: The algorithm used to encrypt the JWE's payload. + /// - Returns: A fully initialized `Encrypter` or `nil` if provided key is of the wrong type. + @available(*, deprecated, message: "Use `init?(keyEncryptionAlgorithm:encryptionKey:contentEncyptionAlgorithm:)` instead") + public init?(keyEncryptionAlgorithm: AsymmetricKeyAlgorithm, keyEncryptionKey kek: KeyType, contentEncyptionAlgorithm: SymmetricKeyAlgorithm) { + self.init(keyEncryptionAlgorithm: keyEncryptionAlgorithm, encryptionKey: kek, contentEncyptionAlgorithm: contentEncyptionAlgorithm) + } + internal func encrypt(header: JWEHeader, payload: Payload) throws -> EncryptionContext { guard let alg = header.algorithm, alg == asymmetric.algorithm else { throw JWEError.keyEncryptionAlgorithmMismatch @@ -95,9 +119,14 @@ public struct Encrypter { throw JWEError.contentEncryptionAlgorithmMismatch } - let cek = try SecureRandom.generate(count: enc.keyLength) + let cek = try symmetric.symmetricKey ?? SecureRandom.generate(count: enc.keyLength) + let encryptedKey = try asymmetric.encrypt(cek) - let symmetricContext = try symmetric.encrypt(payload.data(), with: cek, additionalAuthenticatedData: header.data().base64URLEncodedData()) + let symmetricContext = try symmetric.encrypt( + payload.data(), + with: cek, + additionalAuthenticatedData: header.data().base64URLEncodedData() + ) return EncryptionContext( encryptedKey: encryptedKey, diff --git a/JOSESwift/Sources/JOSESwiftError.swift b/JOSESwift/Sources/JOSESwiftError.swift index 85890ae8..07061ce3 100644 --- a/JOSESwift/Sources/JOSESwiftError.swift +++ b/JOSESwift/Sources/JOSESwiftError.swift @@ -24,4 +24,5 @@ public enum JOSESwiftError: Error { case modulusNotBase64URLUIntEncoded case exponentNotBase64URLUIntEncoded case privateExponentNotBase64URLUIntEncoded + case symmetricKeyNotBase64URLEncoded } diff --git a/JOSESwift/Sources/JWE.swift b/JOSESwift/Sources/JWE.swift index 28e44b4c..37a39270 100644 --- a/JOSESwift/Sources/JWE.swift +++ b/JOSESwift/Sources/JWE.swift @@ -132,12 +132,13 @@ public struct JWE { } /// Decrypt the JWE's ciphertext and return the corresponding plaintext. - /// As mentioned it is the responsibility of the user to cache this plaintext. + /// It is the responsibility of the user to cache this plaintext. /// /// - Parameter kdk: The private key to decrypt the JWE with. /// - Returns: The decrypted payload of the JWE. /// - Throws: A `JOSESwiftError` indicating any errors. - public func decrypt(with kdk: KeyType) throws -> Payload { + @available(*, deprecated, message: "Use `decrypt(using decrypter:)` instead") + public func decrypt(with key: KeyType) throws -> Payload { let context = DecryptionContext( header: header, encryptedKey: encryptedKey, @@ -150,7 +151,7 @@ public struct JWE { throw JOSESwiftError.decryptingFailed(description: "Invalid header parameter.") } - guard let decrypter = Decrypter(keyDecryptionAlgorithm: alg, keyDecryptionKey: kdk, contentDecryptionAlgorithm: enc) else { + guard let decrypter = Decrypter(keyDecryptionAlgorithm: alg, decryptionKey: key, contentDecryptionAlgorithm: enc) else { throw JOSESwiftError.decryptingFailed(description: "Wrong key type.") } @@ -160,6 +161,35 @@ public struct JWE { throw JOSESwiftError.decryptingFailed(description: error.localizedDescription) } } + + /// Decrypt the JWE's ciphertext and return the corresponding plaintext. + /// It is the responsibility of the user to cache this plaintext. + /// + /// - Parameter decrypter: The decrypter to decrypt the JWE with. + /// - Returns: The decrypted payload of the JWE. + /// - Throws: A `JOSESwiftError` indicating any errors. + public func decrypt(using decrypter: Decrypter) throws -> Payload { + let context = DecryptionContext( + header: header, + encryptedKey: encryptedKey, + initializationVector: initializationVector, + ciphertext: ciphertext, + authenticationTag: authenticationTag + ) + + guard + decrypter.asymmetric.algorithm == header.algorithm, + decrypter.symmetric.algorithm == header.encryptionAlgorithm + else { + throw JOSESwiftError.decryptingFailed(description: "JWE header algorithms do not match encrypter algorithms.") + } + + do { + return Payload(try decrypter.decrypt(context)) + } catch { + throw JOSESwiftError.decryptingFailed(description: error.localizedDescription) + } + } } /// Serialize the JWE to a given compact serializer. diff --git a/JOSESwift/Sources/JWK.swift b/JOSESwift/Sources/JWK.swift index 22573363..7c18dcd4 100644 --- a/JOSESwift/Sources/JWK.swift +++ b/JOSESwift/Sources/JWK.swift @@ -41,6 +41,7 @@ internal enum JWKError: Error { /// - RSA public enum JWKKeyType: String, Codable { case RSA = "RSA" + case OCT = "oct" } /// A JWK object that represents a key or a key pair of a certain type. diff --git a/JOSESwift/Sources/JWKParameters.swift b/JOSESwift/Sources/JWKParameters.swift index c13b95ed..7f913201 100644 --- a/JOSESwift/Sources/JWKParameters.swift +++ b/JOSESwift/Sources/JWKParameters.swift @@ -44,3 +44,9 @@ public enum RSAParameter: String, CodingKey { case exponent = "e" case privateExponent = "d" } + +/// Symmetric key specific JWK parameters. +/// See [RFC-7518, Section 6.3](https://tools.ietf.org/html/rfc7518#section-6.4) for details. +public enum SymmetricKeyParameter: String, CodingKey { + case key = "k" +} diff --git a/JOSESwift/Sources/JWKSetCodable.swift b/JOSESwift/Sources/JWKSetCodable.swift index c8938b21..30bfe7a1 100644 --- a/JOSESwift/Sources/JWKSetCodable.swift +++ b/JOSESwift/Sources/JWKSetCodable.swift @@ -40,6 +40,9 @@ extension JWKSet: Encodable { case is RSAPrivateKey: // swiftlint:disable:next force_cast try keyContainer.encode(key as! RSAPrivateKey) + case is SymmetricKey: + // swiftlint:disable:next force_cast + try keyContainer.encode(key as! SymmetricKey) default: break } @@ -54,22 +57,26 @@ extension JWKSet: Decodable { var keys: [JWK] = [] while !keyContainer.isAtEnd { - var key: JWK? - do { - key = try keyContainer.decode(RSAPrivateKey.self) - } catch DecodingError.keyNotFound(RSAParameter.privateExponent, _) { - key = try keyContainer.decode(RSAPublicKey.self) + if let key = try? keyContainer.decode(RSAPrivateKey.self) { + keys.append(key) + continue + } + + if let key = try? keyContainer.decode(RSAPublicKey.self) { + keys.append(key) + continue } - guard let rsaKey = key else { - throw DecodingError.dataCorruptedError(in: keyContainer, debugDescription: """ - No RSAPublicKey or RSAPrivateKey found to decode. - """ - ) + if let key = try? keyContainer.decode(SymmetricKey.self) { + keys.append(key) + continue } - keys.append(rsaKey) + throw DecodingError.dataCorruptedError(in: keyContainer, debugDescription: """ + No RSAPrivateKey, RSAPublicKey, or SymmetricKey found to decode. + """ + ) } self.init(keys: keys) diff --git a/JOSESwift/Sources/JWS.swift b/JOSESwift/Sources/JWS.swift index 75a657ee..dfafe77a 100644 --- a/JOSESwift/Sources/JWS.swift +++ b/JOSESwift/Sources/JWS.swift @@ -112,6 +112,7 @@ public struct JWS { /// - Parameter publicKey: The public key whose corresponding private key signed the JWS. /// - Returns: `true` if the JWS's signature is valid for the given key and the JWS's header and payload. /// `false` if the signature is not valid or if the singature could not be verified. + @available(*, deprecated, message: "Use `isValid(for verifier:)` instead") public func isValid(for publicKey: KeyType) -> Bool { guard let alg = header.algorithm else { return false @@ -133,6 +134,7 @@ public struct JWS { /// - Parameter publicKey: The public key whose corresponding private key signed the JWS. /// - Returns: The JWS on which this function was called if the signature is valid. /// - Throws: A `JOSESwiftError` if the signature is invalid or if errors occured during signature validation. + @available(*, deprecated, message: "Use `validate(using verifier:)` instead") public func validate(with publicKey: KeyType) throws -> JWS { guard let alg = header.algorithm else { throw JOSESwiftError.verifyingFailed(description: "Invalid header parameter.") @@ -152,6 +154,44 @@ public struct JWS { return self } + + /// Checks whether the JWS's signature is valid using a given verifier. + /// + /// - Parameter verifier: The verifier containing the public key whose corresponding private key signed the JWS. + /// - Returns: The JWS on which this function was called if the signature is valid. + /// - Throws: A `JOSESwiftError` if the signature is invalid or if errors occured during signature validation. + public func validate(using verifier: Verifier) throws -> JWS { + guard verifier.verifier.algorithm == header.algorithm else { + throw JOSESwiftError.verifyingFailed(description: "JWS header algorithm does not match verifier algorithm.") + } + + do { + guard try verifier.verify(header: header, and: payload, against: signature) else { + throw JOSESwiftError.signatureInvalid + } + } catch { + throw JOSESwiftError.verifyingFailed(description: error.localizedDescription) + } + + return self + } + + /// Checks whether the JWS's signature is valid using a given verifier. + /// + /// - Parameter verifier: The verifier containing the public key whose corresponding private key signed the JWS. + /// - Returns: `true` if the JWS's signature is valid for the given verifier and the JWS's header and payload. + /// `false` if the signature is not valid or if the singature could not be verified. + public func isValid(for verifier: Verifier) -> Bool { + guard verifier.verifier.algorithm == header.algorithm else { + return false + } + + do { + return try verifier.verify(header: header, and: payload, against: signature) + } catch { + return false + } + } } extension JWS: CompactSerializable { diff --git a/JOSESwift/Sources/RSADecrypter.swift b/JOSESwift/Sources/RSADecrypter.swift index 36acdcb1..1832a7e2 100644 --- a/JOSESwift/Sources/RSADecrypter.swift +++ b/JOSESwift/Sources/RSADecrypter.swift @@ -28,9 +28,19 @@ internal struct RSADecrypter: AsymmetricDecrypter { typealias KeyType = RSA.KeyType let algorithm: AsymmetricKeyAlgorithm - let privateKey: KeyType + let privateKey: KeyType? + + init(algorithm: AsymmetricKeyAlgorithm, privateKey: KeyType? = nil) { + self.algorithm = algorithm + self.privateKey = privateKey + } func decrypt(_ ciphertext: Data) throws -> Data { + guard let privateKey = privateKey else { + // If no key is set, we're using direct encryption so the encrypted key is empty. + return Data() + } + return try RSA.decrypt(ciphertext, with: privateKey, and: algorithm) } } diff --git a/JOSESwift/Sources/RSAEncrypter.swift b/JOSESwift/Sources/RSAEncrypter.swift index 7b829062..4b1d4e44 100644 --- a/JOSESwift/Sources/RSAEncrypter.swift +++ b/JOSESwift/Sources/RSAEncrypter.swift @@ -28,9 +28,19 @@ internal struct RSAEncrypter: AsymmetricEncrypter { typealias KeyType = RSA.KeyType let algorithm: AsymmetricKeyAlgorithm - let publicKey: KeyType + let publicKey: KeyType? + + init(algorithm: AsymmetricKeyAlgorithm, publicKey: KeyType? = nil) { + self.algorithm = algorithm + self.publicKey = publicKey + } func encrypt(_ plaintext: Data) throws -> Data { + guard let publicKey = publicKey else { + // If no key is set, we're using direct encryption so the encrypted key is empty. + return Data() + } + return try RSA.encrypt(plaintext, with: publicKey, and: algorithm) } } diff --git a/JOSESwift/Sources/RSAKeys.swift b/JOSESwift/Sources/RSAKeys.swift index d1f53709..d9da63bb 100644 --- a/JOSESwift/Sources/RSAKeys.swift +++ b/JOSESwift/Sources/RSAKeys.swift @@ -148,7 +148,8 @@ public struct RSAPublicKey: JWK { additionalParameters: parameters ) } - + + /// Creates an `RSAPublicKey` from the JSON representation of a public key JWK. public init(data: Data) throws { self = try JSONDecoder().decode(RSAPublicKey.self, from: data) } @@ -243,6 +244,7 @@ public struct RSAPrivateKey: JWK { ) } + /// Creates an `RSAPrivateKey` from the JSON representation of a private key JWK. public init(data: Data) throws { self = try JSONDecoder().decode(RSAPrivateKey.self, from: data) } diff --git a/JOSESwift/Sources/SymmetricKeyCodable.swift b/JOSESwift/Sources/SymmetricKeyCodable.swift new file mode 100644 index 00000000..bc8b9889 --- /dev/null +++ b/JOSESwift/Sources/SymmetricKeyCodable.swift @@ -0,0 +1,78 @@ +// +// SymmetricKeyCodable.swift +// JOSESwift +// +// Created by Daniel Egger on 10.07.18. +// +// --------------------------------------------------------------------------- +// Copyright 2018 Airside Mobile Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// --------------------------------------------------------------------------- +// + +import Foundation + +extension SymmetricKey: Encodable { + public func encode(to encoder: Encoder) throws { + var commonParameters = encoder.container(keyedBy: JWKParameter.self) + + // The key type parameter is required. + try commonParameters.encode(keyType, forKey: .keyType) + + // Other common parameters are optional. + for parameter in parameters { + // Only encode known parameters. + if let key = JWKParameter(rawValue: parameter.key) { + try commonParameters.encode(parameter.value, forKey: key) + } + } + + // Symmetric key specific parameters. + var symmetricKeyParameters = encoder.container(keyedBy: SymmetricKeyParameter.self) + try symmetricKeyParameters.encode(key, forKey: .key) + } +} + +extension SymmetricKey: Decodable { + public init(from decoder: Decoder) throws { + let commonParameters = try decoder.container(keyedBy: JWKParameter.self) + + // The key type parameter is required. + guard try commonParameters.decode(String.self, forKey: .keyType) == JWKKeyType.OCT.rawValue else { + throw DecodingError.keyNotFound( + JWKParameter.keyType, + DecodingError.Context.init( + codingPath: [JWKParameter.keyType], + debugDescription: "Wrong parameter: key type" + ) + ) + } + + // Other common parameters are optional. + var parameters: [String: String] = [:] + for key in commonParameters.allKeys { + parameters[key.rawValue] = try commonParameters.decode(String.self, forKey: key) + } + + // RSA public key specific parameters. + let symmetricKeyParameters = try decoder.container(keyedBy: SymmetricKeyParameter.self) + let key = try symmetricKeyParameters.decode(String.self, forKey: .key) + + guard let keyData = Data(base64URLEncoded: key) else { + throw JOSESwiftError.symmetricKeyNotBase64URLEncoded + } + + self.init(key: keyData, additionalParameters: parameters) + } +} diff --git a/JOSESwift/Sources/SymmetricKeys.swift b/JOSESwift/Sources/SymmetricKeys.swift new file mode 100644 index 00000000..46ff9a0b --- /dev/null +++ b/JOSESwift/Sources/SymmetricKeys.swift @@ -0,0 +1,121 @@ +// +// SymmetricKeys.swift +// JOSESwift +// +// Created by Daniel Egger on 10.07.18. +// +// --------------------------------------------------------------------------- +// Copyright 2018 Airside Mobile Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// --------------------------------------------------------------------------- +// + +import Foundation + +// MARK: Protocols + +/// The components of a symmetric key. +public typealias SymmetricKeyComponents = ( + Data +) + +/// A type that represents a symmetric key. +/// It can be expressed through `SymmetricKeyComponents` meaning it can be converted to such components +/// and it can be created from such components. +public protocol ExpressibleAsSymmetricKeyComponents { + + /// Creates an object that contains the supplied components. + /// + /// - Parameter components: The symmetric key components. + /// - Returns: An object containing the supplied components. + /// - Throws: A `JOSESwiftError` indicating any errors. + static func representing(symmetricKeyComponents components: SymmetricKeyComponents) throws -> Self + + /// Extracts the symmetric key components. + /// + /// - Returns: The components of the symmetric key. + /// - Throws: A `JOSESwiftError` indicating any errors. + func symmetricKeyComponents() throws -> SymmetricKeyComponents +} + +// MARK: Key + +/// A JWK holding a symmetric key. +public struct SymmetricKey: JWK { + /// The JWK key type. + public let keyType: JWKKeyType + + /// The JWK parameters. + public let parameters: [String: String] + + /// The symmetric key represented as + /// base64url encoding of the octet sequence containing the key data. + public let key: String + + /// Initializes a JWK containing a symmetric key. + /// + /// - Parameters: + /// - key: The octet sequence containing the key data. + /// - parameters: Additional JWK parameters. + public init(key: Data, additionalParameters parameters: [String: String] = [:]) { + self.keyType = .OCT + self.key = key.base64URLEncodedString() + + self.parameters = parameters.merging( + [ + JWKParameter.keyType.rawValue: self.keyType.rawValue, + SymmetricKeyParameter.key.rawValue: self.key, + ], + uniquingKeysWith: { (_, new) in new } + ) + } + + /// Creates a `SymmetricKey` JWK with the specified symmetric key and optional additional JWK parameters. + /// + /// - Parameters: + /// - key: The symmetirc key that the resulting JWK should represent. + /// - parameters: Any additional parameters to be contained in the JWK. + /// - Throws: A `JOSESwiftError` indicating any errors. + public init(key: ExpressibleAsSymmetricKeyComponents, additionalParameters parameters: [String: String] = [:]) throws { + guard let components = try? key.symmetricKeyComponents() else { + throw JOSESwiftError.couldNotConstructJWK + } + + self.init( + key: components, + additionalParameters: parameters + ) + } + + /// Creates a `SymmetricKey` from the JSON representation of a symmetric key JWK. + public init(data: Data) throws { + self = try JSONDecoder().decode(SymmetricKey.self, from: data) + } + + /// Converts the `SymmetricKey` JWK to the specified type. + /// The specified type must conform to the `ExpressibleAsSymmetricKeyComponents` protocol. + /// + /// **Example:** + /// `let keyData = try jwk.converted(to: Data.self)` + /// + /// - Parameter type: The type to convert the JWK to. + /// - Returns: The type initialized with the key data. + /// - Throws: A `JOSESwiftError` indicating any errors. + public func converted(to type: T.Type) throws -> T where T: ExpressibleAsSymmetricKeyComponents { + guard let keyData = Data(base64URLEncoded: key) else { + throw JOSESwiftError.symmetricKeyNotBase64URLEncoded + } + return try T.representing(symmetricKeyComponents: (keyData)) + } +} diff --git a/JOSESwift/Sources/Verifier.swift b/JOSESwift/Sources/Verifier.swift index cd3f742b..051e3cf6 100644 --- a/JOSESwift/Sources/Verifier.swift +++ b/JOSESwift/Sources/Verifier.swift @@ -36,7 +36,7 @@ protocol VerifierProtocol { func verify(_ signingInput: Data, against signature: Data) throws -> Bool } -public struct Verifier { +public struct Verifier { let verifier: VerifierProtocol /// Constructs a verifyer used to verify a JWS. @@ -45,9 +45,9 @@ public struct Verifier { /// - signingAlgorithm: A desired `SignatureAlgorithm`. /// - privateKey: The public key used to verify the JWS's signature. Currently supported key types are: `SecKey`. /// - Returns: A fully initialized `Verifier` or `nil` if provided key is of the wrong type. - public init?(verifyingAlgorithm: SignatureAlgorithm, publicKey: KeyType) { + public init?(verifyingAlgorithm: SignatureAlgorithm, publicKey: KeyType) { switch verifyingAlgorithm { - case .RS256,.RS512: + case .RS256, .RS512: guard type(of: publicKey) is RSAVerifier.KeyType.Type else { return nil } diff --git a/README.md b/README.md index 34aef00a..4571d4d2 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Encrypting and decrypting arbitrary data using the JWE standard. | RSA1_5 | RSA-OAEP | RSA-OAEP-256 | A128KW | A192KW | A256KW | dir | ECDH-ES | ECDH-ES+A128KW | ECDH-ES+A192KW | ECDH-ES+A256KW | A128GCMKW | A192GCMKW | A256GCMKW | PBES2-HS256+A128KW | PBES2-HS384+A192KW | PBES2-HS512+A256KW | | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | -| :white_check_mark: | | | | | | | | | | | | | | | | | +| :white_check_mark: | | | | | | :white_check_mark: | | | | | | | | | | | *Supported content encryption algorithms:* @@ -75,7 +75,7 @@ Encoding and decoding RSA public key data in PKCS#1 format as well as iOS `SecKe | EC | RSA | oct | | :--: | :--: | :--: | -| | :white_check_mark: | | +| | :white_check_mark: | :white_check_mark: | ## Installation @@ -169,7 +169,8 @@ let serialization = "ey (..) n0.HK (..) pQ.yS (..) PA.AK (..) Jx.hB (..) 7w" ``` swift do { let jws = try JWS(compactSerialization: serialization) - let payload = try jws.validate(with: publicKey).payload + let verifier = Verifier(verifyingAlgorithm: .RS512, publicKey: publicKey)! + let payload = try jws.validate(using: verifier).payload let message = String(data: payload.data(), encoding: .utf8)! print(message) // Summer ⛱, Sun ☀️, Cactus 🌵 @@ -204,7 +205,7 @@ let header = JWEHeader(algorithm: .RSA1_5, encryptionAlgorithm: .A256CBCHS512) let payload = Payload(message) // Encrypter algorithms must match header algorithms. -let encrypter = Encrypter(keyEncryptionAlgorithm: .RSA1_5, keyEncryptionKey: publicKey, contentEncyptionAlgorithm: .A256CBCHS512)! +let encrypter = Encrypter(keyEncryptionAlgorithm: .RSA1_5, encryptionKey: publicKey, contentEncyptionAlgorithm: .A256CBCHS512)! ``` ``` swift @@ -228,7 +229,8 @@ let serialization = "ey (..) n0.HK (..) pQ.yS (..) PA.AK (..) Jx.hB (..) 7w" ``` swift do { let jwe = try JWE(compactSerialization: serialization) - let payload = try jwe.decrypt(with: privateKey) + let decrypter = Decrypter(keyDecryptionAlgorithm: .RSA1_5, decryptionKey: privateKey, contentDecryptionAlgorithm: .A256CBCHS512)! + let payload = try jwe.decrypt(using: decrypter) let message = String(data: payload.data(), encoding: .utf8)! print(message) // Summer ⛱, Sun ☀️, Cactus 🌵 diff --git a/Tests/CryptoTestCase.swift b/Tests/CryptoTestCase.swift index 4a8e73ef..c9e21b72 100644 --- a/Tests/CryptoTestCase.swift +++ b/Tests/CryptoTestCase.swift @@ -25,11 +25,14 @@ import XCTest class CryptoTestCase: XCTestCase { let message = "The true sign of intelligence is not knowledge but imagination." - let privateKey2048Tag = "com.airsidemobile.JOSESwift.testPrivateKey2048" + let privateKeyAlice2048Tag = "com.airsidemobile.JOSESwift.testprivateKeyAlice2048" + let privateKeyBob2048Tag = "com.airsidemobile.JOSESwift.testprivateKeyBob2048" let privateKey4096Tag = "com.airsidemobile.JOSESwift.testPrivateKey4096" - var privateKey2048: SecKey? - var publicKey2048: SecKey? + var privateKeyAlice2048: SecKey? + var privateKeyBob2048: SecKey? + var publicKeyAlice2048: SecKey? + var publicKeyBob2048: SecKey? var privateKey4096: SecKey? var publicKey4096: SecKey? @@ -50,7 +53,8 @@ class CryptoTestCase: XCTestCase { ky8g """ - var publicKey2048Data: Data! + var publicKeyAlice2048Data: Data! + var publicKeyBob2048Data: Data! var publicKey4096Data: Data! // Generated by OpenSSL for `publicKey` (without leading 0x00). @@ -133,19 +137,28 @@ class CryptoTestCase: XCTestCase { private func setupKeys() { if - let path = Bundle(for: type(of: self)).path(forResource: "TestKey", ofType: "plist"), + let path = Bundle(for: type(of: self)).path(forResource: "TestKeys", ofType: "plist"), let keyDict = NSDictionary(contentsOfFile: path), - let keyData2048 = Data(base64Encoded: keyDict[privateKey2048Tag] as! String), + let keyDataAlice2048 = Data(base64Encoded: keyDict[privateKeyAlice2048Tag] as! String), + let keyDataBob2048 = Data(base64Encoded: keyDict[privateKeyBob2048Tag] as! String), let keyData4096 = Data(base64Encoded: keyDict[privateKey4096Tag] as! String) { - // 2048 + // 2048 - Alice - let keyPair2048 = setupSecKeyPair(size: 2048, data: keyData2048, tag: privateKey2048Tag)! + let keyPairAlice2048 = setupSecKeyPair(size: 2048, data: keyDataAlice2048, tag: privateKeyAlice2048Tag)! - privateKey2048 = keyPair2048.privateKey - publicKey2048 = keyPair2048.publicKey - publicKey2048Data = SecKeyCopyExternalRepresentation(publicKey2048!, nil)! as Data + privateKeyAlice2048 = keyPairAlice2048.privateKey + publicKeyAlice2048 = keyPairAlice2048.publicKey + publicKeyAlice2048Data = SecKeyCopyExternalRepresentation(publicKeyAlice2048!, nil)! as Data + + // 2048 - Bob + + let keyPairBob2048 = setupSecKeyPair(size: 2048, data: keyDataBob2048, tag: privateKeyBob2048Tag)! + + privateKeyBob2048 = keyPairBob2048.privateKey + publicKeyBob2048 = keyPairBob2048.publicKey + publicKeyBob2048Data = SecKeyCopyExternalRepresentation(publicKeyBob2048!, nil)! as Data // 4096 diff --git a/Tests/DataRSAPublicKeyTests.swift b/Tests/DataRSAPublicKeyTests.swift index acf5c141..67707c52 100644 --- a/Tests/DataRSAPublicKeyTests.swift +++ b/Tests/DataRSAPublicKeyTests.swift @@ -27,14 +27,14 @@ import XCTest class DataRSAPublicKeyTests: CryptoTestCase { func testLeadingZeroDropped() { - let components = try! publicKey2048Data.rsaPublicKeyComponents() + let components = try! publicKeyAlice2048Data.rsaPublicKeyComponents() - XCTAssertEqual(try! [UInt8](publicKey2048Data).read(.sequence).read(.integer).first!, 0x00) + XCTAssertEqual(try! [UInt8](publicKeyAlice2048Data).read(.sequence).read(.integer).first!, 0x00) XCTAssertNotEqual([UInt8](components.modulus).first!, 0x00) } - func testPublicKey2048Modulus() { - let components = try? publicKey2048Data.rsaPublicKeyComponents() + func testpublicKeyAlice2048Modulus() { + let components = try? publicKeyAlice2048Data.rsaPublicKeyComponents() XCTAssertNotNil(components) @@ -43,8 +43,8 @@ class DataRSAPublicKeyTests: CryptoTestCase { XCTAssertEqual(modulus, expectedModulus2048Data) } - func testPublicKey2048Exponent() { - let components = try? publicKey2048Data.rsaPublicKeyComponents() + func testpublicKeyAlice2048Exponent() { + let components = try? publicKeyAlice2048Data.rsaPublicKeyComponents() XCTAssertNotNil(components) @@ -77,7 +77,7 @@ class DataRSAPublicKeyTests: CryptoTestCase { let components = (expectedModulus2048Data, expectedExponentData) let data = try! Data.representing(rsaPublicKeyComponents: components) - let expectedData = publicKey2048Data + let expectedData = publicKeyAlice2048Data XCTAssertEqual(data, expectedData) } diff --git a/Tests/EncrypterDecrypterInitializationTests.swift b/Tests/EncrypterDecrypterInitializationTests.swift new file mode 100644 index 00000000..14c582e7 --- /dev/null +++ b/Tests/EncrypterDecrypterInitializationTests.swift @@ -0,0 +1,91 @@ +// +// EncrypterDecrypterInitializationTests.swift +// Tests +// +// Created by Daniel Egger on 17.07.18. +// +// --------------------------------------------------------------------------- +// Copyright 2018 Airside Mobile Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// --------------------------------------------------------------------------- +// + +import XCTest +@testable import JOSESwift + +class EncrypterDecrypterInitializationTests: CryptoTestCase { + + @available(*, deprecated) + func testEncrypterDeprecatedRSAInitialization() { + XCTAssertNotNil( + Encrypter(keyEncryptionAlgorithm: .RSA1_5, keyEncryptionKey: publicKeyAlice2048!, contentEncyptionAlgorithm: .A256CBCHS512) + ) + } + + func testEncrypterNewRSAInitialization() { + XCTAssertNotNil( + Encrypter(keyEncryptionAlgorithm: .RSA1_5, encryptionKey: publicKeyAlice2048!, contentEncyptionAlgorithm: .A256CBCHS512) + ) + } + + func testEncrypterRSAInitializationWrongAlgorithm() { + XCTAssertNil( + Encrypter(keyEncryptionAlgorithm: .direct, encryptionKey: publicKeyAlice2048!, contentEncyptionAlgorithm: .A256CBCHS512) + ) + } + + func testEncrypterRSAInitializationWrongKeyType() { + XCTAssertNil( + Encrypter(keyEncryptionAlgorithm: .RSA1_5, encryptionKey: Data(), contentEncyptionAlgorithm: .A256CBCHS512) + ) + } + + func testEncrypterDirectInitializationWrongKeyType() { + XCTAssertNil( + Encrypter(keyEncryptionAlgorithm: .direct, encryptionKey: publicKeyAlice2048!, contentEncyptionAlgorithm: .A256CBCHS512) + ) + } + + @available(*, deprecated) + func testDecrypterDeprecatedRSAInitialization() { + XCTAssertNotNil( + Decrypter(keyDecryptionAlgorithm: .RSA1_5, keyDecryptionKey: privateKeyAlice2048!, contentDecryptionAlgorithm: .A256CBCHS512) + ) + } + + func testDecrypterNewRSAInitialization() { + XCTAssertNotNil( + Decrypter(keyDecryptionAlgorithm: .RSA1_5, decryptionKey: privateKeyAlice2048!, contentDecryptionAlgorithm: .A256CBCHS512) + ) + } + + func testDecrypterRSAInitializationWrongAlgorithm() { + XCTAssertNil( + Decrypter(keyDecryptionAlgorithm: .direct, decryptionKey: privateKeyAlice2048!, contentDecryptionAlgorithm: .A256CBCHS512) + ) + } + + func testDecrypterRSAInitializationWrongKeyType() { + XCTAssertNil( + Decrypter(keyDecryptionAlgorithm: .RSA1_5, decryptionKey: Data(), contentDecryptionAlgorithm: .A256CBCHS512) + ) + } + + func testDecrypterDirectInitializationWrongKeyType() { + XCTAssertNil( + Decrypter(keyDecryptionAlgorithm: .direct, decryptionKey: privateKeyAlice2048!, contentDecryptionAlgorithm: .A256CBCHS512) + ) + } + +} diff --git a/Tests/JWEDirectEncryptionTests.swift b/Tests/JWEDirectEncryptionTests.swift new file mode 100644 index 00000000..d98ba056 --- /dev/null +++ b/Tests/JWEDirectEncryptionTests.swift @@ -0,0 +1,141 @@ +// +// JWEDirectEncryptionTests.swift +// Tests +// +// Created by Daniel Egger on 04.07.18. +// +// --------------------------------------------------------------------------- +// Copyright 2018 Airside Mobile Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// --------------------------------------------------------------------------- +// + +import XCTest +@testable import JOSESwift + +class JWEDirectEncryptionTests: CryptoTestCase { + + let data = "So Secret! 🔥🌵".data(using: .utf8)! + + let serializationFromNimbus = """ + eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiZGlyIn0..HUTNQ9m2Z8Q77tQJhLs5gg.DWQCCkrCPFeZ2-65L9__z83N1exh4oVIk4rOO2_\ + v1eE.8sOW54Soupo_-TdXg5A9qXvokaHzS8cGb__ca3MvuEo + """ + + let keyFromNimbus = Data(bytes: [ + 177, 119, 33, 13, 164, 30, 108, 121, + 207, 136, 107, 242, 12, 224, 19, 226, + 198, 134, 17, 71, 173, 75, 42, 61, + 48, 162, 206, 161, 97, 108, 185, 234, + 60, 181, 90, 85, 51, 123, 6, 224, + 4, 122, 29, 230, 151, 12, 244, 127, + 121, 25, 4, 85, 220, 144, 215, 110, + 130, 17, 68, 228, 129, 138, 7, 130 + ]) + + @available(*, deprecated) + func testRoundtrip() { + let symmetricKey = try! SecureRandom.generate(count: SymmetricKeyAlgorithm.A256CBCHS512.keyLength) + + let header = JWEHeader(algorithm: .direct, encryptionAlgorithm: .A256CBCHS512) + let payload = Payload(data) + let encrypter = Encrypter(keyEncryptionAlgorithm: .direct, encryptionKey: symmetricKey, contentEncyptionAlgorithm: .A256CBCHS512)! + + let jwe = try! JWE(header: header, payload: payload, encrypter: encrypter) + let serialization = jwe.compactSerializedString + + try! XCTAssertEqual(JWE(compactSerialization: serialization).decrypt(with: symmetricKey).data(), data) + } + + @available(*, deprecated) + func testDecryptFromNimbus() { + let symmetricKey = keyFromNimbus + + let jwe = try! JWE(compactSerialization: serializationFromNimbus) + + try! XCTAssertEqual(jwe.decrypt(with: symmetricKey).data(), data) + } + + @available(*, deprecated) + func testDecryptWithWrongSymmetricKey() { + let symmetricKey = try! SecureRandom.generate(count: SymmetricKeyAlgorithm.A256CBCHS512.keyLength) + + let jwe = try! JWE(compactSerialization: serializationFromNimbus) + + XCTAssertThrowsError(try jwe.decrypt(with: symmetricKey)) + } + + @available(*, deprecated) + func testDecryptWithCorrectAlgWrongKeyType() { + let privateKey = privateKeyAlice2048! + + let jwe = try! JWE(compactSerialization: serializationFromNimbus) + + XCTAssertThrowsError(try jwe.decrypt(with: privateKey)) + } + + @available(*, deprecated) + func testDecryptWithWrongAlgCorrectKeyType() { + // replacing `{"enc":"A256CBC-HS512","alg":"dir"}` with `{"enc":"A256CBC-HS512","alg":"RSA1_5"}` + let serialization = serializationFromNimbus.replacingOccurrences( + of: "eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiZGlyIn0", + with: "eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiUlNBMV81In0" + ) + + let symmetricKey = keyFromNimbus + + let jwe = try! JWE(compactSerialization: serialization) + + XCTAssertThrowsError(try jwe.decrypt(with: symmetricKey)) + } + + @available(*, deprecated) + func testDecryptWithWrongAlgWrongKeyType() { + // replacing `{"enc":"A256CBC-HS512","alg":"dir"}` with `{"enc":"A256CBC-HS512","alg":"RSA1_5"}` + let serialization = serializationFromNimbus.replacingOccurrences( + of: "eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiZGlyIn0", + with: "eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiUlNBMV81In0" + ) + + let privateKey = privateKeyAlice2048! + + let jwe = try! JWE(compactSerialization: serialization) + + XCTAssertThrowsError(try jwe.decrypt(with: privateKey)) + } + + @available(*, deprecated) + func testDecryptWithEncryptedKeyPresent() { + let encryptedKey = """ + c3HOjtBLx3xt3RYMx2WexgbYpcszeqiWXZmeBaLIUb8BXsETRxHDFUyyAt6Q8dIYX22kQs9Kte7AL1CcVxS0C2sx_yu7xDZ4s67cHW1AMbf\ + qqqhyaUSS5BkyTIhLgEbo34ohxP0bYq-enlu8hlOYWhwh-yLSj1mRCSYufv8ik6QhoJ14P981M_O8Fl0XMGe7Ki3jdui_MKj8NKN-96McS4\ + 0zhtxZRuq1ZYzmmu1fAh3MA5LZkUBInnW5GpNfar3Lap1UnIt1yTJf9U9zk48qU9ymPnbD8oYm8ec15lsmCuuMcB1uG3SgFYAGTZStgX1My\ + KjyAlDGiZrKo6p0Hn8piw + """ + + var serialization = serializationFromNimbus + var parts = serialization.split(separator: ".").map { + String($0) + } + parts.insert(encryptedKey, at: 1) + serialization = parts.joined(separator: ".") + + let symmetricKey = keyFromNimbus + + let jwe = try! JWE(compactSerialization: serialization) + + XCTAssertThrowsError(try jwe.decrypt(with: symmetricKey)) + } + +} diff --git a/Tests/JWEHeaderTests.swift b/Tests/JWEHeaderTests.swift index ac793683..ab7e91cf 100644 --- a/Tests/JWEHeaderTests.swift +++ b/Tests/JWEHeaderTests.swift @@ -25,8 +25,11 @@ import XCTest @testable import JOSESwift class JWEHeaderTests: XCTestCase { - let parameterDict = ["alg": "RSA1_5", "enc": "A256CBC-HS512"] - let parameterData = try! JSONSerialization.data(withJSONObject: ["alg": "RSA1_5", "enc": "A256CBC-HS512"], options: []) + let parameterDictRSA = ["alg": "RSA1_5", "enc": "A256CBC-HS512"] + let parameterDataRSA = try! JSONSerialization.data(withJSONObject: ["alg": "RSA1_5", "enc": "A256CBC-HS512"], options: []) + + let parameterDictDirect = ["alg": "dir", "enc": "A256CBC-HS512"] + let parameterDataDirect = try! JSONSerialization.data(withJSONObject: ["alg": "dir", "enc": "A256CBC-HS512"], options: []) override func setUp() { super.setUp() @@ -36,16 +39,16 @@ class JWEHeaderTests: XCTestCase { super.tearDown() } - func testInitWithParameters() { - let header = try! JWEHeader(parameters: parameterDict, headerData: parameterData) + func testInitRSAWithParameters() { + let header = try! JWEHeader(parameters: parameterDictRSA, headerData: parameterDataRSA) XCTAssertEqual(header.parameters["enc"] as? String, SymmetricKeyAlgorithm.A256CBCHS512.rawValue) XCTAssertEqual(header.parameters["alg"] as? String, AsymmetricKeyAlgorithm.RSA1_5.rawValue) - XCTAssertEqual(header.data(), try! JSONSerialization.data(withJSONObject: parameterDict, options: [])) + XCTAssertEqual(header.data(), try! JSONSerialization.data(withJSONObject: parameterDictRSA, options: [])) } - func testInitWithData() { - let data = try! JSONSerialization.data(withJSONObject: parameterDict, options: []) + func testInitRSAWithData() { + let data = try! JSONSerialization.data(withJSONObject: parameterDictRSA, options: []) let header = JWEHeader(data)! XCTAssertEqual(header.parameters["enc"] as? String, SymmetricKeyAlgorithm.A256CBCHS512.rawValue) @@ -53,10 +56,27 @@ class JWEHeaderTests: XCTestCase { XCTAssertEqual(header.data(), data) } + func testInitDirectWithParameters() { + let header = try! JWEHeader(parameters: parameterDictDirect, headerData: parameterDataDirect) + + XCTAssertEqual(header.parameters["enc"] as? String, SymmetricKeyAlgorithm.A256CBCHS512.rawValue) + XCTAssertEqual(header.parameters["alg"] as? String, AsymmetricKeyAlgorithm.direct.rawValue) + XCTAssertEqual(header.data(), try! JSONSerialization.data(withJSONObject: parameterDictDirect, options: [])) + } + + func testInitDirectWithData() { + let data = try! JSONSerialization.data(withJSONObject: parameterDictDirect, options: []) + let header = JWEHeader(data)! + + XCTAssertEqual(header.parameters["enc"] as? String, SymmetricKeyAlgorithm.A256CBCHS512.rawValue) + XCTAssertEqual(header.parameters["alg"] as? String, AsymmetricKeyAlgorithm.direct.rawValue) + XCTAssertEqual(header.data(), data) + } + func testInitWithAlgAndEnc() { let header = JWEHeader(algorithm: .RSA1_5, encryptionAlgorithm: .A256CBCHS512) - XCTAssertEqual(header.data(), try! JSONSerialization.data(withJSONObject: parameterDict, options: [])) + XCTAssertEqual(header.data(), try! JSONSerialization.data(withJSONObject: parameterDictRSA, options: [])) XCTAssertEqual(header.parameters["alg"] as? String, AsymmetricKeyAlgorithm.RSA1_5.rawValue) XCTAssertEqual(header.parameters["enc"] as? String, SymmetricKeyAlgorithm.A256CBCHS512.rawValue) diff --git a/Tests/JWERSATests.swift b/Tests/JWERSATests.swift new file mode 100644 index 00000000..4b3f4bd8 --- /dev/null +++ b/Tests/JWERSATests.swift @@ -0,0 +1,171 @@ +// +// JWETests.swift +// Tests +// +// Created by Carol Capek on 31.10.17. +// +// --------------------------------------------------------------------------- +// Copyright 2018 Airside Mobile Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// --------------------------------------------------------------------------- +// + +import XCTest +@testable import JOSESwift + +class JWETests: CryptoTestCase { + + let compactSerializedJWE = """ + eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0.Od5AMgOHu6rcEYWkX7w_x_wnMlM5JfZaszCC4xtLGYU9d0BnPm95UWUrgSh\ + StGH6LHMxpGdru6gXpdxfhhrji12vUIzmkbyNW5M9wjx2t0e4pzzBSYxgOzFoa3jT9a0PcZfyqHIeTrcrTHtpSJ_CIDiZ3MIeqA7hjuRqu2YcTA\ + E0v5TPLhHDVRBptkOggA5SL2-gRuUuYoWdanMw_JTHK4utXQZoSY1LTdub_Fh5ez1RqOouc3an5Hx6ImzyJS_cbO_l9xHpHjE7in6SeV9bAZTaY\ + EaGnjGKEVaGQ7JiwtTA5rDfVQ5RHSn6blB2Hh5Am7mKzssYu9JjUmr3T-ez_g.M6QnlRxQQ5YS2rF4-wwT3g.4GAtq6fJWJt249SEuK5P_3xJGN\ + YP_e_rhz0PVg9QnJXiRl030ggI9GGs3E_0pEPBs9_WJ3E60qQVoXTIMbJXSQ.bQc-W1Ph_0_3kX570pT8gjDlGyiK3kF8PlHiT7GWfMo + """.data(using: .utf8)! + + let plaintext = """ + The true sign of intelligence is not knowledge but imagination. + """.data(using: .utf8)! + + @available(*, deprecated) + func testJWERoundtrip() { + let header = JWEHeader(algorithm: .RSA1_5, encryptionAlgorithm: .A256CBCHS512) + let payload = Payload(message.data(using: .utf8)!) + let encrypter = Encrypter(keyEncryptionAlgorithm: .RSA1_5, encryptionKey: publicKeyAlice2048!, contentEncyptionAlgorithm: .A256CBCHS512)! + let jweEnc = try! JWE(header: header, payload: payload, encrypter: encrypter) + + let jweDec = try! JWE(compactSerialization: jweEnc.compactSerializedData) + let decryptedPayload = try! jweDec.decrypt(with: privateKeyAlice2048!) + + XCTAssertEqual(message.data(using: .utf8)!, decryptedPayload.data()) + } + + @available(*, deprecated) + func testDecryptWithInferredDecrypter() { + let jwe = try! JWE(compactSerialization: compactSerializedJWE) + let payload = try! jwe.decrypt(with: privateKeyAlice2048!).data() + + XCTAssertEqual(payload, plaintext) + } + + @available(*, deprecated) + func testDecryptFails() { + let header = JWEHeader(algorithm: .RSA1_5, encryptionAlgorithm: .A256CBCHS512) + let payload = Payload(message.data(using: .utf8)!) + let encrypter = Encrypter(keyEncryptionAlgorithm: .RSA1_5, encryptionKey: publicKeyAlice2048!, contentEncyptionAlgorithm: .A256CBCHS512)! + let jweEnc = try! JWE(header: header, payload: payload, encrypter: encrypter) + + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeRSA, + kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, + kSecAttrKeySizeInBits as String: 2048, + kSecPrivateKeyAttrs as String: [ + kSecAttrIsPermanent as String: false, + kSecAttrApplicationTag as String: privateKeyAlice2048Tag + ] + ] + + var error: Unmanaged? + + guard let key = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { + print(error!) + return + } + + let jweDec = try! JWE(compactSerialization: jweEnc.compactSerializedData) + + XCTAssertThrowsError(try jweDec.decrypt(with: key)) + } + + func testDecryptWithExplicitDecrypter() { + let jwe = try! JWE(compactSerialization: compactSerializedJWE) + + let decrypter = Decrypter( + keyDecryptionAlgorithm: .RSA1_5, + decryptionKey: privateKeyAlice2048!, + contentDecryptionAlgorithm: .A256CBCHS512 + )! + + XCTAssertEqual(try! jwe.decrypt(using: decrypter).data(), plaintext) + } + + func testDecryptWithExplicitDecrypterWrongAlgInHeader() { + // Replaces alg "RSA1_5" with alg "RSA-OAEP" in header + let malformedSerialization = String(data: compactSerializedJWE, encoding: .utf8)!.replacingOccurrences( + of: "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0", + with: "eyJhbGciOiAiUlNBLU9BRVAiLCJlbmMiOiAiQTI1NkNCQy1IUzUxMiJ9" + ).data(using: .utf8)! + + let jwe = try! JWE(compactSerialization: malformedSerialization) + + let decrypter = Decrypter( + keyDecryptionAlgorithm: .RSA1_5, + decryptionKey: privateKeyAlice2048!, + contentDecryptionAlgorithm: .A256CBCHS512 + )! + + XCTAssertThrowsError(try jwe.decrypt(using: decrypter), "decrypting with wrong alg in header") { error in + XCTAssertEqual(error as! JOSESwiftError, JOSESwiftError.decryptingFailed(description: "JWE header algorithms do not match encrypter algorithms.")) + } + } + + func testDecryptWithExplicitDecrypterWrongEncInHeader() { + // Replaces enc "A256CBC-HS512" with enc "A128GCM" in header + let malformedSerialization = String(data: compactSerializedJWE, encoding: .utf8)!.replacingOccurrences( + of: "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0", + with: "eyJhbGciOiAiUlNBMV81IiwiZW5jIjogIkExMjhHQ00ifQ" + ).data(using: .utf8)! + + let jwe = try! JWE(compactSerialization: malformedSerialization) + + let decrypter = Decrypter( + keyDecryptionAlgorithm: .RSA1_5, + decryptionKey: privateKeyAlice2048!, + contentDecryptionAlgorithm: .A256CBCHS512 + )! + + XCTAssertThrowsError(try jwe.decrypt(using: decrypter), "decrypting with wrong enc in header") { error in + XCTAssertEqual(error as! JOSESwiftError, JOSESwiftError.decryptingFailed(description: "JWE header algorithms do not match encrypter algorithms.")) + } + } + + func testDecryptWithExplicitDecrypterFailsForWrongKey() { + let jwe = try! JWE(compactSerialization: compactSerializedJWE) + + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeRSA, + kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, + kSecAttrKeySizeInBits as String: 2048, + kSecPrivateKeyAttrs as String: [ + kSecAttrIsPermanent as String: false, + ] + ] + + var error: Unmanaged? + + guard let key = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { + print(error!) + return + } + + let decrypter = Decrypter( + keyDecryptionAlgorithm: .RSA1_5, + decryptionKey: key, + contentDecryptionAlgorithm: .A256CBCHS512 + )! + + XCTAssertThrowsError(try jwe.decrypt(using: decrypter)) + } + +} diff --git a/Tests/JWETests.swift b/Tests/JWETests.swift deleted file mode 100644 index 7862b697..00000000 --- a/Tests/JWETests.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// JWETests.swift -// Tests -// -// Created by Carol Capek on 31.10.17. -// -// --------------------------------------------------------------------------- -// Copyright 2018 Airside Mobile Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// --------------------------------------------------------------------------- -// - -import XCTest -@testable import JOSESwift - -class JWETests: CryptoTestCase { - - override func setUp() { - super.setUp() - } - - override func tearDown() { - super.tearDown() - } - - func testJWERoundtrip() { - let header = JWEHeader(algorithm: .RSA1_5, encryptionAlgorithm: .A256CBCHS512) - let payload = Payload(message.data(using: .utf8)!) - let encrypter = Encrypter(keyEncryptionAlgorithm: .RSA1_5, keyEncryptionKey: publicKey2048!, contentEncyptionAlgorithm: .A256CBCHS512)! - let jweEnc = try! JWE(header: header, payload: payload, encrypter: encrypter) - - let jweDec = try! JWE(compactSerialization: jweEnc.compactSerializedData) - let decryptedPayload = try! jweDec.decrypt(with: privateKey2048!) - - XCTAssertEqual(message.data(using: .utf8)!, decryptedPayload.data()) - } - - func testDecrypt() { - let compactSerializedJWE = "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0.Od5AMgOHu6rcEYWkX7w_x_wnMlM5JfZaszCC4xtLGYU9d0BnPm95UWUrgShStGH6LHMxpGdru6gXpdxfhhrji12vUIzmkbyNW5M9wjx2t0e4pzzBSYxgOzFoa3jT9a0PcZfyqHIeTrcrTHtpSJ_CIDiZ3MIeqA7hjuRqu2YcTAE0v5TPLhHDVRBptkOggA5SL2-gRuUuYoWdanMw_JTHK4utXQZoSY1LTdub_Fh5ez1RqOouc3an5Hx6ImzyJS_cbO_l9xHpHjE7in6SeV9bAZTaYEaGnjGKEVaGQ7JiwtTA5rDfVQ5RHSn6blB2Hh5Am7mKzssYu9JjUmr3T-ez_g.M6QnlRxQQ5YS2rF4-wwT3g.4GAtq6fJWJt249SEuK5P_3xJGNYP_e_rhz0PVg9QnJXiRl030ggI9GGs3E_0pEPBs9_WJ3E60qQVoXTIMbJXSQ.bQc-W1Ph_0_3kX570pT8gjDlGyiK3kF8PlHiT7GWfMo" - let jwe = try! JWE(compactSerialization: compactSerializedJWE) - let payloadString = String(data: (try! jwe.decrypt(with: privateKey2048!)).data(), encoding: .utf8)! - - XCTAssertEqual(payloadString, "The true sign of intelligence is not knowledge but imagination.") - } - - func testDecryptFails() { - let header = JWEHeader(algorithm: .RSA1_5, encryptionAlgorithm: .A256CBCHS512) - let payload = Payload(message.data(using: .utf8)!) - let encrypter = Encrypter(keyEncryptionAlgorithm: .RSA1_5, keyEncryptionKey: publicKey2048!, contentEncyptionAlgorithm: .A256CBCHS512)! - let jweEnc = try! JWE(header: header, payload: payload, encrypter: encrypter) - - let attributes: [String: Any] = [ - kSecAttrKeyType as String: kSecAttrKeyTypeRSA, - kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, - kSecAttrKeySizeInBits as String: 2048, - kSecPrivateKeyAttrs as String: [ - kSecAttrIsPermanent as String: false, - kSecAttrApplicationTag as String: privateKey2048Tag - ] - ] - - var error: Unmanaged? - - guard let key = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { - print(error!) - return - } - - let jweDec = try! JWE(compactSerialization: jweEnc.compactSerializedData) - - XCTAssertThrowsError(try jweDec.decrypt(with: key)) - } -} diff --git a/Tests/JWKRSAEncodingTests.swift b/Tests/JWKRSAEncodingTests.swift index af5a0d6b..2028df92 100644 --- a/Tests/JWKRSAEncodingTests.swift +++ b/Tests/JWKRSAEncodingTests.swift @@ -27,7 +27,7 @@ import XCTest class JWKRSAEncodingTests: CryptoTestCase { func testPublicKeyEncoding() { - let jwk = try! RSAPublicKey(publicKey: publicKey2048!, additionalParameters: [ + let jwk = try! RSAPublicKey(publicKey: publicKeyAlice2048!, additionalParameters: [ "alg": "RS256", "kid": "2011-04-29" ]) @@ -47,7 +47,7 @@ class JWKRSAEncodingTests: CryptoTestCase { } func testEncodingPublicKeyWithUnregisteredParameter() { - let jwk = try! RSAPublicKey(publicKey: publicKey2048!, additionalParameters: [ + let jwk = try! RSAPublicKey(publicKey: publicKeyAlice2048!, additionalParameters: [ "alg": "RS256", "kid": "2011-04-29", "breeze": "through" diff --git a/Tests/JWKRSAKeysTests.swift b/Tests/JWKRSAKeysTests.swift index 70e612a7..377cdc51 100644 --- a/Tests/JWKRSAKeysTests.swift +++ b/Tests/JWKRSAKeysTests.swift @@ -27,7 +27,7 @@ import XCTest class JWKRSAKeysTests: CryptoTestCase { func testMergingDuplicateAdditionalParametersInPublicKey() { - let jwk = try! RSAPublicKey(publicKey: publicKey2048!, additionalParameters: [ + let jwk = try! RSAPublicKey(publicKey: publicKeyAlice2048!, additionalParameters: [ "kty": "wrongKty" ]) @@ -81,7 +81,7 @@ class JWKRSAKeysTests: CryptoTestCase { } func testPublicKeyKeyTypeIsPresent() { - let jwk = try! RSAPublicKey(publicKey: publicKey2048!) + let jwk = try! RSAPublicKey(publicKey: publicKeyAlice2048!) XCTAssertEqual(jwk.keyType, .RSA) XCTAssertEqual(jwk[JWKParameter.keyType.rawValue] ?? "", JWKKeyType.RSA.rawValue) @@ -97,7 +97,7 @@ class JWKRSAKeysTests: CryptoTestCase { } func testSettingAndGettingAdditionalParameter() { - let jwk = try! RSAPublicKey(publicKey: publicKey2048!, additionalParameters: [ + let jwk = try! RSAPublicKey(publicKey: publicKeyAlice2048!, additionalParameters: [ "kid": "new on the block" ]) @@ -105,7 +105,7 @@ class JWKRSAKeysTests: CryptoTestCase { } func testPublicKeyAllParametersArePresentInDict() { - let jwk = try! RSAPublicKey(publicKey: publicKey2048!, additionalParameters: [ + let jwk = try! RSAPublicKey(publicKey: publicKeyAlice2048!, additionalParameters: [ "kid": "new on the block", "use": "test" ]) diff --git a/Tests/JWKSetCodingTests.swift b/Tests/JWKSetCodingTests.swift index 1a86f081..718c3512 100644 --- a/Tests/JWKSetCodingTests.swift +++ b/Tests/JWKSetCodingTests.swift @@ -48,12 +48,30 @@ class JWKSetCodingTests: XCTestCase { kfz0Y6mqnOYtqc0X4jfcKoAC8Q """ - let additionalParameters = [ + let additionalParametersRSA = [ "kty": "RSA", "alg": "RS256", "kid": "2011-04-29" ] + let firstSymmetricKey = Data( + base64URLEncoded: "GawgguFyGrWKav7AX4VKUg" + )! + + let firstAdditionalParametersOct = [ + "kty": "oct", + "alg": "A128KW" + ] + + let secondSymmetricKey = Data( + base64URLEncoded: "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow" + )! + + let secondAdditionalParametersOct = [ + "kty": "oct", + "kid": "HMAC key used in JWS spec Appendix A.1 example" + ] + let testDataOneRSAPublicKey = """ {"keys":[{"alg":"RS256","e":"AQAB","kid":"2011-04-29","kty":"RSA","n":"0vx7agoe\ bGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_\ @@ -95,11 +113,40 @@ class JWKSetCodingTests: XCTestCase { Fd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"}]} """.data(using: .utf8)! + let testDataOneSymmetricKey = """ + {"keys":[{"alg":"A128KW","k":"GawgguFyGrWKav7AX4VKUg","kty":"oct"}]} + """.data(using: .utf8)! + + let testDataTwoSymmetricKeys = """ + {"keys":[{"alg":"A128KW","k":"GawgguFyGrWKav7AX4VKUg","kty":"oct"},{"k":"AyM1Sys\ + PpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"\ + ,"kid":"HMAC key used in JWS spec Appendix A.1 example","kty":"oct"}]} + """.data(using: .utf8)! + + let testDataRSAPublicAndPrivateAndSymmetricKey = """ + {"keys":[{"alg":"RS256","e":"AQAB","kid":"2011-04-29","kty":"RSA","n":"0vx7agoe\ + bGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_\ + BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-\ + 65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD\ + 08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-\ + kEgU8awapJzKnqDKgw"},{"alg":"RS256","d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3Ea\ + G6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijw\ + p3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6\ + TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z\ + 4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q","e":"AQAB"\ + ,"kid":"2011-04-29","kty":"RSA","n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFb\ + WhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ\ + _2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8\ + KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4l\ + Fd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"},{"alg":"A128K\ + W","k":"GawgguFyGrWKav7AX4VKUg","kty":"oct"}]} + """.data(using: .utf8)! + // - MARK: Encoding Tests func testEncodingOneRSAPublicKey() { let set: JWKSet = [ - RSAPublicKey(modulus: modulus, exponent: exponent, additionalParameters: additionalParameters) + RSAPublicKey(modulus: modulus, exponent: exponent, additionalParameters: additionalParametersRSA) ] let encoder = JSONEncoder() @@ -116,10 +163,49 @@ class JWKSetCodingTests: XCTestCase { XCTAssertEqual(try! encoder.encode(set), testDataOneRSAPublicKey) } + func testEncodingOneSymmetricKey() { + let set: JWKSet = [ + SymmetricKey(key: firstSymmetricKey, additionalParameters: firstAdditionalParametersOct) + ] + + let encoder = JSONEncoder() + + // Sorting keys is needed to compare the encoding outcome with the already sorted test data. + // Otherwise the encoding is correct but because the order of keys is not defined, the encoding outcome and + // the test data can differ. + if #available(iOS 11.0, *) { + encoder.outputFormatting = .sortedKeys + } else { + XCTFail("Tests need to be executed on iOS11 or above to obatin sorted JSON keys for comparison.") + } + + XCTAssertEqual(try! encoder.encode(set), testDataOneSymmetricKey) + } + + func testEncodingTwoSymmetricKeys() { + let set: JWKSet = [ + SymmetricKey(key: firstSymmetricKey, additionalParameters: firstAdditionalParametersOct), + SymmetricKey(key: secondSymmetricKey, additionalParameters: secondAdditionalParametersOct) + ] + + let encoder = JSONEncoder() + + // Sorting keys is needed to compare the encoding outcome with the already sorted test data. + // Otherwise the encoding is correct but because the order of keys is not defined, the encoding outcome and + // the test data can differ. + if #available(iOS 11.0, *) { + encoder.outputFormatting = .sortedKeys + } else { + XCTFail("Tests need to be executed on iOS11 or above to obatin sorted JSON keys for comparison.") + } + + XCTAssertEqual(try! encoder.encode(set), testDataTwoSymmetricKeys) + } + func testEncodingTwoRSAPublicKeys() { let set: JWKSet = [ - RSAPublicKey(modulus: modulus, exponent: exponent, additionalParameters: additionalParameters), - RSAPublicKey(modulus: modulus, exponent: exponent, additionalParameters: additionalParameters) + RSAPublicKey(modulus: modulus, exponent: exponent, additionalParameters: additionalParametersRSA), + RSAPublicKey(modulus: modulus, exponent: exponent, additionalParameters: additionalParametersRSA) ] let encoder = JSONEncoder() @@ -138,8 +224,8 @@ class JWKSetCodingTests: XCTestCase { func testEncodingRSAPublicAndPrivateKey() { let set: JWKSet = [ - RSAPublicKey(modulus: modulus, exponent: exponent, additionalParameters: additionalParameters), - RSAPrivateKey(modulus: modulus, exponent: exponent, privateExponent: privateExponent, additionalParameters: additionalParameters) + RSAPublicKey(modulus: modulus, exponent: exponent, additionalParameters: additionalParametersRSA), + RSAPrivateKey(modulus: modulus, exponent: exponent, privateExponent: privateExponent, additionalParameters: additionalParametersRSA) ] let encoder = JSONEncoder() @@ -156,6 +242,27 @@ class JWKSetCodingTests: XCTestCase { XCTAssertEqual(try! encoder.encode(set), testDataRSAPublicAndPrivateKey) } + func testEncodingRSAPublicAndPrivateAndSymmetricKey() { + let set: JWKSet = [ + RSAPublicKey(modulus: modulus, exponent: exponent, additionalParameters: additionalParametersRSA), + RSAPrivateKey(modulus: modulus, exponent: exponent, privateExponent: privateExponent, additionalParameters: additionalParametersRSA), + SymmetricKey(key: firstSymmetricKey, additionalParameters: firstAdditionalParametersOct) + ] + + let encoder = JSONEncoder() + + // Sorting keys is needed to compare the encoding outcome with the already sorted test data. + // Otherwise the encoding is correct but because the order of keys is not defined, the encoding outcome and + // the test data can differ. + if #available(iOS 11.0, *) { + encoder.outputFormatting = .sortedKeys + } else { + XCTFail("Tests need to be executed on iOS11 or above to obatin sorted JSON keys for comparison.") + } + + XCTAssertEqual(try! encoder.encode(set), testDataRSAPublicAndPrivateAndSymmetricKey) + } + // - MARK: Decoding Tests func testDecodingOneRSAPublicKey() { @@ -169,9 +276,23 @@ class JWKSetCodingTests: XCTestCase { XCTAssertEqual(rsaKey.modulus, modulus) XCTAssertEqual(rsaKey.exponent, exponent) - XCTAssertEqual(rsaKey["kty"], additionalParameters["kty"]) - XCTAssertEqual(rsaKey["kid"], additionalParameters["kid"]) - XCTAssertEqual(rsaKey["alg"], additionalParameters["alg"]) + XCTAssertEqual(rsaKey["kty"], additionalParametersRSA["kty"]) + XCTAssertEqual(rsaKey["kid"], additionalParametersRSA["kid"]) + XCTAssertEqual(rsaKey["alg"], additionalParametersRSA["alg"]) + } + + func testDecodingOneSymmetricKey() { + let jwkSet = try! JSONDecoder().decode(JWKSet.self, from: testDataOneSymmetricKey) + + XCTAssertEqual(jwkSet.keys.count, 1) + + XCTAssert(jwkSet[0] is SymmetricKey) + + let key = jwkSet[0] as! SymmetricKey + + XCTAssertEqual(key.key, firstSymmetricKey.base64URLEncodedString()) + XCTAssertEqual(key["kty"], firstAdditionalParametersOct["kty"]) + XCTAssertEqual(key["alg"], firstAdditionalParametersOct["alg"]) } func testDecodingTwoRSAPublicKeys() { @@ -185,9 +306,9 @@ class JWKSetCodingTests: XCTestCase { XCTAssertEqual(rsaKey.modulus, modulus) XCTAssertEqual(rsaKey.exponent, exponent) - XCTAssertEqual(rsaKey["kty"], additionalParameters["kty"]) - XCTAssertEqual(rsaKey["kid"], additionalParameters["kid"]) - XCTAssertEqual(rsaKey["alg"], additionalParameters["alg"]) + XCTAssertEqual(rsaKey["kty"], additionalParametersRSA["kty"]) + XCTAssertEqual(rsaKey["kid"], additionalParametersRSA["kid"]) + XCTAssertEqual(rsaKey["alg"], additionalParametersRSA["alg"]) XCTAssert(jwkSet[1] is RSAPublicKey) @@ -195,9 +316,9 @@ class JWKSetCodingTests: XCTestCase { XCTAssertEqual(rsaKey.modulus, modulus) XCTAssertEqual(rsaKey.exponent, exponent) - XCTAssertEqual(rsaKey["kty"], additionalParameters["kty"]) - XCTAssertEqual(rsaKey["kid"], additionalParameters["kid"]) - XCTAssertEqual(rsaKey["alg"], additionalParameters["alg"]) + XCTAssertEqual(rsaKey["kty"], additionalParametersRSA["kty"]) + XCTAssertEqual(rsaKey["kid"], additionalParametersRSA["kid"]) + XCTAssertEqual(rsaKey["alg"], additionalParametersRSA["alg"]) } func testDecodingRSAPublicAndPrivateKey() { @@ -211,9 +332,9 @@ class JWKSetCodingTests: XCTestCase { XCTAssertEqual(rsaPublicKey.modulus, modulus) XCTAssertEqual(rsaPublicKey.exponent, exponent) - XCTAssertEqual(rsaPublicKey["kty"], additionalParameters["kty"]) - XCTAssertEqual(rsaPublicKey["kid"], additionalParameters["kid"]) - XCTAssertEqual(rsaPublicKey["alg"], additionalParameters["alg"]) + XCTAssertEqual(rsaPublicKey["kty"], additionalParametersRSA["kty"]) + XCTAssertEqual(rsaPublicKey["kid"], additionalParametersRSA["kid"]) + XCTAssertEqual(rsaPublicKey["alg"], additionalParametersRSA["alg"]) XCTAssert(jwkSet[1] is RSAPrivateKey) @@ -222,9 +343,44 @@ class JWKSetCodingTests: XCTestCase { XCTAssertEqual(rsaPrivateKey.modulus, modulus) XCTAssertEqual(rsaPrivateKey.exponent, exponent) XCTAssertEqual(rsaPrivateKey.privateExponent, privateExponent) - XCTAssertEqual(rsaPrivateKey["kty"], additionalParameters["kty"]) - XCTAssertEqual(rsaPrivateKey["kid"], additionalParameters["kid"]) - XCTAssertEqual(rsaPrivateKey["alg"], additionalParameters["alg"]) + XCTAssertEqual(rsaPrivateKey["kty"], additionalParametersRSA["kty"]) + XCTAssertEqual(rsaPrivateKey["kid"], additionalParametersRSA["kid"]) + XCTAssertEqual(rsaPrivateKey["alg"], additionalParametersRSA["alg"]) + } + + func testDecodingRSAPublicAndPrivateAndSymmetricKey() { + let jwkSet = try! JSONDecoder().decode(JWKSet.self, from: testDataRSAPublicAndPrivateAndSymmetricKey) + + XCTAssertEqual(jwkSet.keys.count, 3) + + XCTAssert(jwkSet[0] is RSAPublicKey) + + let rsaPublicKey = jwkSet[0] as! RSAPublicKey + + XCTAssertEqual(rsaPublicKey.modulus, modulus) + XCTAssertEqual(rsaPublicKey.exponent, exponent) + XCTAssertEqual(rsaPublicKey["kty"], additionalParametersRSA["kty"]) + XCTAssertEqual(rsaPublicKey["kid"], additionalParametersRSA["kid"]) + XCTAssertEqual(rsaPublicKey["alg"], additionalParametersRSA["alg"]) + + XCTAssert(jwkSet[1] is RSAPrivateKey) + + let rsaPrivateKey = jwkSet[1] as! RSAPrivateKey + + XCTAssertEqual(rsaPrivateKey.modulus, modulus) + XCTAssertEqual(rsaPrivateKey.exponent, exponent) + XCTAssertEqual(rsaPrivateKey.privateExponent, privateExponent) + XCTAssertEqual(rsaPrivateKey["kty"], additionalParametersRSA["kty"]) + XCTAssertEqual(rsaPrivateKey["kid"], additionalParametersRSA["kid"]) + XCTAssertEqual(rsaPrivateKey["alg"], additionalParametersRSA["alg"]) + + XCTAssert(jwkSet[2] is SymmetricKey) + + let key = jwkSet[2] as! SymmetricKey + + XCTAssertEqual(key.key, firstSymmetricKey.base64URLEncodedString()) + XCTAssertEqual(key["kty"], firstAdditionalParametersOct["kty"]) + XCTAssertEqual(key["alg"], firstAdditionalParametersOct["alg"]) } // - MARK: Convenience Helpers Test @@ -240,9 +396,9 @@ class JWKSetCodingTests: XCTestCase { XCTAssertEqual(rsaKey.modulus, modulus) XCTAssertEqual(rsaKey.exponent, exponent) - XCTAssertEqual(rsaKey["kty"], additionalParameters["kty"]) - XCTAssertEqual(rsaKey["kid"], additionalParameters["kid"]) - XCTAssertEqual(rsaKey["alg"], additionalParameters["alg"]) + XCTAssertEqual(rsaKey["kty"], additionalParametersRSA["kty"]) + XCTAssertEqual(rsaKey["kid"], additionalParametersRSA["kid"]) + XCTAssertEqual(rsaKey["alg"], additionalParametersRSA["alg"]) XCTAssert(jwkSet[1] is RSAPublicKey) @@ -250,9 +406,9 @@ class JWKSetCodingTests: XCTestCase { XCTAssertEqual(rsaKey.modulus, modulus) XCTAssertEqual(rsaKey.exponent, exponent) - XCTAssertEqual(rsaKey["kty"], additionalParameters["kty"]) - XCTAssertEqual(rsaKey["kid"], additionalParameters["kid"]) - XCTAssertEqual(rsaKey["alg"], additionalParameters["alg"]) + XCTAssertEqual(rsaKey["kty"], additionalParametersRSA["kty"]) + XCTAssertEqual(rsaKey["kid"], additionalParametersRSA["kid"]) + XCTAssertEqual(rsaKey["alg"], additionalParametersRSA["alg"]) } func testToJsonData() { diff --git a/Tests/JWKtoJSONTests.swift b/Tests/JWKtoJSONTests.swift index 374eab45..de8f826d 100644 --- a/Tests/JWKtoJSONTests.swift +++ b/Tests/JWKtoJSONTests.swift @@ -27,7 +27,7 @@ import XCTest class JWKtoJSONTests: CryptoTestCase { func testJSONString() { - let jwk = try! RSAPublicKey(publicKey: publicKey2048!, additionalParameters: [ + let jwk = try! RSAPublicKey(publicKey: publicKeyAlice2048!, additionalParameters: [ "alg": "RS256", "kid": "2011-04-29" ]) @@ -48,7 +48,7 @@ class JWKtoJSONTests: CryptoTestCase { } func testJSONData() { - let jwk = try! RSAPublicKey(publicKey: publicKey2048!, additionalParameters: [ + let jwk = try! RSAPublicKey(publicKey: publicKeyAlice2048!, additionalParameters: [ "alg": "RS256", "kid": "2011-04-29" ]) diff --git a/Tests/JWSTests.swift b/Tests/JWSTests.swift index 4a1d51ac..b361302e 100644 --- a/Tests/JWSTests.swift +++ b/Tests/JWSTests.swift @@ -33,6 +33,7 @@ class JWSTests: CryptoTestCase { super.tearDown() } + @available(*, deprecated) func testSignAndSerializeRS256() { self.performTestRSASign(algorithm: .RS256, compactSerializedJWS: compactSerializedJWSRS256Const) } @@ -41,6 +42,7 @@ class JWSTests: CryptoTestCase { self.performTestRSADeserialization(algorithm: .RS256, compactSerializedJWS: compactSerializedJWSRS256Const) } + @available(*, deprecated) func testSignAndSerializeRS512() { self.performTestRSASign(algorithm: .RS512, compactSerializedJWS: compactSerializedJWSRS512Const) } @@ -51,15 +53,16 @@ class JWSTests: CryptoTestCase { // MARK: - RSA Tests + @available(*, deprecated) private func performTestRSASign(algorithm: SignatureAlgorithm, compactSerializedJWS: String) { - guard publicKey2048 != nil, privateKey2048 != nil else { + guard publicKeyAlice2048 != nil, privateKeyAlice2048 != nil else { XCTFail() return } let header = JWSHeader(algorithm: algorithm) let payload = Payload(message.data(using: .utf8)!) - let signer = Signer(signingAlgorithm: algorithm, privateKey: privateKey2048!)! + let signer = Signer(signingAlgorithm: algorithm, privateKey: privateKeyAlice2048!)! let jws = try! JWS(header: header, payload: payload, signer: signer) let compactSerializedJWS = jws.compactSerializedString @@ -67,11 +70,11 @@ class JWSTests: CryptoTestCase { let secondJWS = try! JWS(compactSerialization: compactSerializedJWS) - XCTAssertTrue(secondJWS.isValid(for: publicKey2048!)) + XCTAssertTrue(secondJWS.isValid(for: publicKeyAlice2048!)) } private func performTestRSADeserialization(algorithm: SignatureAlgorithm, compactSerializedJWS: String) { - guard privateKey2048 != nil else { + guard privateKeyAlice2048 != nil else { XCTFail() return } @@ -80,7 +83,7 @@ class JWSTests: CryptoTestCase { XCTAssertEqual(String(data: jws.header.data(), encoding: .utf8), "{\"alg\":\"\(algorithm.rawValue)\"}") XCTAssertEqual(String(data: jws.payload.data(), encoding: .utf8), "The true sign of intelligence is not knowledge but imagination.") - let signer = Signer(signingAlgorithm: algorithm, privateKey: privateKey2048!)! + let signer = Signer(signingAlgorithm: algorithm, privateKey: privateKeyAlice2048!)! let signature = try! signer.sign(header: JWSHeader(algorithm: algorithm), payload: Payload(message.data(using: .utf8)!)) XCTAssertEqual(jws.signature.data(), signature) } diff --git a/Tests/JWSValidationTests.swift b/Tests/JWSValidationTests.swift index 5cc5d23f..0aa0d354 100644 --- a/Tests/JWSValidationTests.swift +++ b/Tests/JWSValidationTests.swift @@ -26,72 +26,227 @@ import XCTest class JWSValidationTests: CryptoTestCase { + @available(*, deprecated) func testIsValid() { let jws = try! JWS(compactSerialization: compactSerializedJWSRS512Const) - XCTAssertTrue(jws.isValid(for: publicKey2048!)) + XCTAssertTrue(jws.isValid(for: publicKeyAlice2048!)) } + @available(*, deprecated) func testIsValidIsFalseForInvalidAlg() { // Replaces alg "RS512" with alg "FOOBAR" in header let malformedSerialization = compactSerializedJWSRS512Const.replacingOccurrences(of: "eyJhbGciOiJSUzUxMiJ9", with: "eyJhbGciOiJGT09CQVIifQ") let jws = try! JWS(compactSerialization: malformedSerialization) - XCTAssertFalse(jws.isValid(for: publicKey2048!)) + XCTAssertFalse(jws.isValid(for: publicKeyAlice2048!)) } + @available(*, deprecated) func testIsValidIsFalseForWrongSignature() { // Replaces part of the signature, making it invalid let malformedSerialization = compactSerializedJWSRS512Const.replacingOccurrences(of: "dar", with: "foo") let jws = try! JWS(compactSerialization: malformedSerialization) - XCTAssertFalse(jws.isValid(for: publicKey2048!)) + XCTAssertFalse(jws.isValid(for: publicKeyAlice2048!)) } + @available(*, deprecated) func testIsValidIsFalseForWrongKey() { let jws = try! JWS(compactSerialization: compactSerializedJWSRS512Const) XCTAssertFalse(jws.isValid(for: publicKey4096!)) } + @available(*, deprecated) func testValidatesDoesNotThrowForValidSignature() { let jws = try! JWS(compactSerialization: compactSerializedJWSRS512Const) - XCTAssertNoThrow(try jws.validate(with: publicKey2048!)) + XCTAssertNoThrow(try jws.validate(with: publicKeyAlice2048!)) } + @available(*, deprecated) func testValidatesReturnsJWS() { let jws = try! JWS(compactSerialization: compactSerializedJWSRS512Const) - let validatedJWS = try! jws.validate(with: publicKey2048!) + let validatedJWS = try! jws.validate(with: publicKeyAlice2048!) XCTAssertEqual(validatedJWS.compactSerializedString, compactSerializedJWSRS512Const) } + @available(*, deprecated) func testValidatesThrowsForInvalidAlg() { // Replaces alg "RS512" with alg "FOOBAR" in header let malformedSerialization = compactSerializedJWSRS512Const.replacingOccurrences(of: "eyJhbGciOiJSUzUxMiJ9", with: "eyJhbGciOiJGT09CQVIifQ") let jws = try! JWS(compactSerialization: malformedSerialization) - XCTAssertThrowsError(try jws.validate(with: publicKey2048!)) + XCTAssertThrowsError(try jws.validate(with: publicKeyAlice2048!)) } + @available(*, deprecated) func testValidatesThrowsForWrongSignature() { // Replaces part of the signature, making it invalid let malformedSerialization = compactSerializedJWSRS512Const.replacingOccurrences(of: "dar", with: "foo") let jws = try! JWS(compactSerialization: malformedSerialization) - XCTAssertThrowsError(try jws.validate(with: publicKey2048!)) + XCTAssertThrowsError(try jws.validate(with: publicKeyAlice2048!)) } + @available(*, deprecated) func testValidatesThrowsForWrongKey() { let jws = try! JWS(compactSerialization: compactSerializedJWSRS512Const) XCTAssertThrowsError(try jws.validate(with: publicKey4096!)) } + + func testValidatesWithExplicitVerifier() { + let jws = try! JWS(compactSerialization: compactSerializedJWSRS512Const) + + let verifier = Verifier(verifyingAlgorithm: .RS512, publicKey: publicKeyAlice2048!)! + + XCTAssertNoThrow(try jws.validate(using: verifier)) + } + + func testValidatesWithExplicitVerifierReturnsJWS() { + let jws = try! JWS(compactSerialization: compactSerializedJWSRS512Const) + + let verifier = Verifier(verifyingAlgorithm: .RS512, publicKey: publicKeyAlice2048!)! + + let returnedJWS = try! jws.validate(using: verifier) + + XCTAssertEqual(returnedJWS.compactSerializedString, jws.compactSerializedString) + } + + func testValidatesWithExplicitVerifierCatchesWrongVerifierAlgorithm() { + let jws = try! JWS(compactSerialization: compactSerializedJWSRS512Const) + + let verifier = Verifier(verifyingAlgorithm: .RS256, publicKey: publicKeyAlice2048!)! + + XCTAssertThrowsError(try jws.validate(using: verifier), "verifying with wrong verifier algorithm") { error in + XCTAssertEqual(error as! JOSESwiftError, JOSESwiftError.verifyingFailed(description: "JWS header algorithm does not match verifier algorithm.")) + } + } + + func testValidatesWithExplicitVerifierCatchesWrongHeaderAlgorithm() { + // Replaces alg "RS512" with alg "HS256" in header + let malformedSerialization = compactSerializedJWSRS512Const.replacingOccurrences(of: "eyJhbGciOiJSUzUxMiJ9", with: "eyJhbGciOiJIUzI1NiJ9") + + let jws = try! JWS(compactSerialization: malformedSerialization) + + let verifier = Verifier(verifyingAlgorithm: .RS512, publicKey: publicKeyAlice2048!)! + + XCTAssertThrowsError(try jws.validate(using: verifier), "verifying with wrong header algorithm") { error in + XCTAssertEqual(error as! JOSESwiftError, JOSESwiftError.verifyingFailed(description: "JWS header algorithm does not match verifier algorithm.")) + } + } + + func testValidatesWithExplicitVerifierFailsForWrongKey() { + let jws = try! JWS(compactSerialization: compactSerializedJWSRS512Const) + + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeRSA, + kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, + kSecAttrKeySizeInBits as String: 2048, + kSecPrivateKeyAttrs as String: [ + kSecAttrIsPermanent as String: false + ] + ] + + var error: Unmanaged? + + guard let key = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { + print(error!) + return + } + + let verifier = Verifier(verifyingAlgorithm: .RS512, publicKey: SecKeyCopyPublicKey(key)!)! + + XCTAssertThrowsError(try jws.validate(using: verifier)) + } + + func testValidatesWithExplicitVerifierFailsForWrongSignature() { + // Replaces part of the signature, making it invalid + let malformedSerialization = compactSerializedJWSRS512Const.replacingOccurrences(of: "dar", with: "foo") + + let jws = try! JWS(compactSerialization: malformedSerialization) + + let verifier = Verifier(verifyingAlgorithm: .RS512, publicKey: publicKeyAlice2048!)! + + XCTAssertThrowsError(try jws.validate(using: verifier)) + } + + func testIsValidWithExplicitVerifier() { + let jws = try! JWS(compactSerialization: compactSerializedJWSRS512Const) + + let verifier = Verifier(verifyingAlgorithm: .RS512, publicKey: publicKeyAlice2048!) + + XCTAssertTrue(jws.isValid(for: verifier!)) + } + + func testIsValidWithExplicitVerifierIsFalseForInvalidAlg() { + // Replaces alg "RS512" with alg "FOOBAR" in header + let malformedSerialization = compactSerializedJWSRS512Const.replacingOccurrences(of: "eyJhbGciOiJSUzUxMiJ9", with: "eyJhbGciOiJGT09CQVIifQ") + + let jws = try! JWS(compactSerialization: malformedSerialization) + + let verifier = Verifier(verifyingAlgorithm: .RS512, publicKey: publicKeyAlice2048!) + + XCTAssertFalse(jws.isValid(for: verifier!)) + } + + func testIsValidWithExplicitVerifierIsFalseForWrongSignature() { + // Replaces part of the signature, making it invalid + let malformedSerialization = compactSerializedJWSRS512Const.replacingOccurrences(of: "dar", with: "foo") + + let jws = try! JWS(compactSerialization: malformedSerialization) + + let verifier = Verifier(verifyingAlgorithm: .RS512, publicKey: publicKeyAlice2048!) + + XCTAssertFalse(jws.isValid(for: verifier!)) + } + + func testIsValidWithExplicitVerifierIsFalseForWrongKey() { + let jws = try! JWS(compactSerialization: compactSerializedJWSRS512Const) + + let verifier = Verifier(verifyingAlgorithm: .RS512, publicKey: publicKey4096!) + + XCTAssertFalse(jws.isValid(for: verifier!)) + } + + func testIsValidWithExplicitVerifierCatchesWrongVerifierAlgorithm() { + let jws = try! JWS(compactSerialization: compactSerializedJWSRS512Const) + + let verifier = Verifier(verifyingAlgorithm: .RS256, publicKey: publicKeyAlice2048!)! + + XCTAssertFalse(jws.isValid(for: verifier)) + } + + func testIsValidWithExplicitVerifierCatchesWrongHeaderAlgorithm() { + // Replaces alg "RS512" with alg "HS256" in header + let malformedSerialization = compactSerializedJWSRS512Const.replacingOccurrences(of: "eyJhbGciOiJSUzUxMiJ9", with: "eyJhbGciOiJIUzI1NiJ9") + + let jws = try! JWS(compactSerialization: malformedSerialization) + + let verifier = Verifier(verifyingAlgorithm: .RS512, publicKey: publicKeyAlice2048!)! + + XCTAssertFalse(jws.isValid(for: verifier)) + } } + +extension JOSESwiftError: Equatable { + public static func ==(lhs: JOSESwiftError, rhs: JOSESwiftError) -> Bool { + switch (lhs, rhs) { + case (.verifyingFailed(let lhs), .verifyingFailed(let rhs)): + return lhs == rhs + case (.decryptingFailed(let lhs), .decryptingFailed(let rhs)): + return lhs == rhs + default: + return false + } + } +} diff --git a/Tests/RSADecrypterTests.swift b/Tests/RSADecrypterTests.swift index f5a0df59..f4def81f 100644 --- a/Tests/RSADecrypterTests.swift +++ b/Tests/RSADecrypterTests.swift @@ -25,7 +25,25 @@ import XCTest @testable import JOSESwift class RSADecrypterTests: CryptoTestCase { - let cipherTextBase64URL = "YkzUN55RBG1igsrL-7ofPPWruTjxNMS3Bl1_a3i-dVKBjiN6kH88j8G3iB3eLWFxbiKZvx0LmkP7J65-frpOgF41SlltjJ62LEZOICm4q21Q4MNqL_Vf7kIjh8DziEJkElEz5W4flhI6YQ-9wW_PQ-coBIPIZiFlw6peKAolz8xcevbUmnqIH6A3hOFLK23J2cWDSWgxHEBIYtZ6whQCJYL4vq5lAFNaEoDaE_cgL6LItY4t-vR1exTJSOlCGAv4uM1Kelk6uitaFk2c0h79u3UpFN_wa02m_PPdgTguRdxwRsCpsQhOKmEakl8LR6NTbIrdB13UoL2tdybltVeUCw" + + // Cipher texts are generated with `openssl rsautl` + // `printf` is used because `echo` appends a newline at the end of the string + + // printf "The true sign of intelligence is not knowledge but imagination." | openssl rsautl -encrypt -pubin -inkey alice.pub.pem -out >(base64) + let cipherTextWithAliceKeyBase64 = """ +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 -pubin -inkey bob.pub.pem -out >(base64) + let cipherTextWithBobKeyBase64 = """ +TA13QruprKdRMt6JVE6dJWKF6bRUZyQLCZKA1KnJCsQx7nprXjYUFlAouhoVfcKPUTuMiyKSMFvkDOqcoJwP3zz14CFA+nI3OeAHiYvMasoJ/H6xlUj1UXh\ +KRZy3cjd581pzxsPKFplBAuUAYacgIpHW+ZuAjGD+KJzQ6N7TFuWUZxXktsIL2mOhvdRWR0Le5pbgBSgkXAOyLUGa66AEZDk42+W7MomNYaDDsxfYHg3LzW\ +sVyhqpFuZQ6hhklG9lJr6OBBuk/+pcJYdHuYEuLnJhPeKqF/9xgMOU0e0xLMtkQW+IfDMlm0oAVavHrxk7A4T5L9+yjuxNjN16k2Rqiw== +""" + + let defaultDecryptionError = RSAError.decryptingFailed(description: "The operation couldn’t be completed. (OSStatus error -50 - RSAdecrypt wrong input (err -1))") override func setUp() { super.setUp() @@ -35,28 +53,134 @@ class RSADecrypterTests: CryptoTestCase { super.tearDown() } - func testDecrypting() { - guard privateKey2048 != nil else { + func testDecryptingWithAliceKey() { + guard privateKeyAlice2048 != nil else { + XCTFail() + return + } + + let decrypter = RSADecrypter(algorithm: .RSA1_5, privateKey: privateKeyAlice2048!) + let decryptedData = try! decrypter.decrypt(Data(base64Encoded: cipherTextWithAliceKeyBase64)!) + let decryptedMessage = String(data: decryptedData, encoding: String.Encoding.utf8) + + XCTAssertEqual(decryptedMessage, message) + } + + func testDecryptingWithBobKey() { + guard privateKeyBob2048 != nil else { XCTFail() return } - let decrypter = RSADecrypter(algorithm: .RSA1_5, privateKey: privateKey2048!) - let plainText = try! decrypter.decrypt(Data(base64URLEncoded: cipherTextBase64URL)!) + let decrypter = RSADecrypter(algorithm: .RSA1_5, privateKey: privateKeyBob2048!) + let decryptedData = try! decrypter.decrypt(Data(base64URLEncoded: cipherTextWithBobKeyBase64)!) + let decryptedMessage = String(data: decryptedData, encoding: String.Encoding.utf8) - XCTAssertEqual(plainText, message.data(using: .utf8)) + XCTAssertEqual(decryptedMessage, message) + } + + func testDecryptingAliceSecretWithBobKey() { + guard privateKeyBob2048 != nil else { + XCTFail() + return + } + + let decrypter = RSADecrypter(algorithm: .RSA1_5, privateKey: privateKeyBob2048!) + + // Decrypting with the wrong key should throw an error + XCTAssertThrowsError(try decrypter.decrypt(Data(base64URLEncoded: cipherTextWithAliceKeyBase64)!)) { (error: Error) in + XCTAssertEqual(error as! RSAError, defaultDecryptionError) + } + } + + func testDecryptingBobSecretWithAliceKey() { + guard privateKeyAlice2048 != nil else { + XCTFail() + return + } + + let decrypter = RSADecrypter(algorithm: .RSA1_5, privateKey: privateKeyAlice2048!) + + // Decrypting with the wrong key should throw an error + XCTAssertThrowsError(try decrypter.decrypt(Data(base64URLEncoded: cipherTextWithBobKeyBase64)!)) { (error: Error) in + XCTAssertEqual(error as! RSAError, defaultDecryptionError) + } } func testCipherTextLengthTooLong() { - guard privateKey2048 != nil else { + guard privateKeyAlice2048 != nil else { XCTFail() return } - let decrypter = RSADecrypter(algorithm: .RSA1_5, privateKey: privateKey2048!) + let decrypter = RSADecrypter(algorithm: .RSA1_5, privateKey: privateKeyAlice2048!) XCTAssertThrowsError(try decrypter.decrypt(Data(count: 300))) { (error: Error) in XCTAssertEqual(error as? RSAError, RSAError.cipherTextLenghtNotSatisfied) } } + func testCipherTextLengthZero() { + guard privateKeyAlice2048 != nil else { + XCTFail() + return + } + + let decrypter = RSADecrypter(algorithm: .RSA1_5, privateKey: privateKeyAlice2048!) + XCTAssertThrowsError(try decrypter.decrypt(Data(count: 0))) { (error: Error) in + XCTAssertEqual(error as? RSAError, RSAError.cipherTextLenghtNotSatisfied) + } + } + + func testCipherTextLengthExactlyRight() { + guard privateKeyAlice2048 != nil else { + XCTFail() + return + } + + // Length checking: If the length of the ciphertext C is not k octets + // (or if k < 11), output "decryption error" and stop. + // See 7.2.2 Decryption operation RSAES-PKCS1-V1_5-DECRYPT (K, C) + // https://tools.ietf.org/html/rfc3447#section-7.2.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() { + guard privateKeyAlice2048 != nil else { + XCTFail() + return + } + + let cipherTextLengthInBytes = SecKeyGetBlockSize(privateKeyAlice2048!) + let testMessage = Data(count: cipherTextLengthInBytes + 1) + + let decrypter = RSADecrypter(algorithm: .RSA1_5, privateKey: privateKeyAlice2048!) + XCTAssertThrowsError(try decrypter.decrypt(testMessage)) { (error: Error) in + XCTAssertEqual(error as? RSAError, RSAError.cipherTextLenghtNotSatisfied) + } + } + + func testCipherTextLengthTooShortByOneByte() { + guard privateKeyAlice2048 != nil else { + XCTFail() + return + } + + let cipherTextLengthInBytes = SecKeyGetBlockSize(privateKeyAlice2048!) + let testMessage = Data(count: cipherTextLengthInBytes - 1) + + let decrypter = RSADecrypter(algorithm: .RSA1_5, 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 4f3b0d2a..30b90ec0 100644 --- a/Tests/RSAEncrypterTests.swift +++ b/Tests/RSAEncrypterTests.swift @@ -31,6 +31,8 @@ extension RSAError: Equatable { return true case (.plainTextLengthNotSatisfied, .plainTextLengthNotSatisfied): return true + case (.decryptingFailed(let a), .decryptingFailed(let b)): + return a == b default: return false } @@ -47,20 +49,84 @@ class RSAEncrypterTests: CryptoTestCase { super.tearDown() } - func testEncrypting() { - guard publicKey2048 != nil, privateKey2048 != nil else { + func testEncryptingWithAliceKey() { + guard publicKeyAlice2048 != nil, privateKeyAlice2048 != nil else { XCTFail() return } - let encrypter = RSAEncrypter(algorithm: .RSA1_5, publicKey: publicKey2048!) + let encrypter = RSAEncrypter(algorithm: .RSA1_5, publicKey: publicKeyAlice2048!) guard let cipherText = try? encrypter.encrypt(message.data(using: .utf8)!) else { XCTFail() return } var decryptionError: Unmanaged? - guard let plainTextData = SecKeyCreateDecryptedData(privateKey2048!, .rsaEncryptionPKCS1, cipherText as CFData, &decryptionError) else { + guard let plainTextData = SecKeyCreateDecryptedData(privateKeyAlice2048!, .rsaEncryptionPKCS1, cipherText as CFData, &decryptionError) else { + XCTFail() + return + } + + XCTAssertEqual(String(data: plainTextData as Data, encoding: .utf8), message) + } + + func testEncryptingTwiceWithAliceKey() { + guard publicKeyAlice2048 != nil, privateKeyAlice2048 != nil else { + XCTFail() + return + } + + let encrypter = RSAEncrypter(algorithm: .RSA1_5, 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() { + guard publicKeyAlice2048 != nil, privateKeyAlice2048 != nil, publicKeyBob2048 != nil, privateKeyBob2048 != nil else { + XCTFail() + return + } + + let encrypterAlice = RSAEncrypter(algorithm: .RSA1_5, publicKey: publicKeyAlice2048!) + let encrypterBob = RSAEncrypter(algorithm: .RSA1_5, 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() { + guard publicKeyBob2048 != nil, privateKeyBob2048 != nil else { + XCTFail() + return + } + + let encrypter = RSAEncrypter(algorithm: .RSA1_5, publicKey: publicKeyBob2048!) + guard let cipherText = try? encrypter.encrypt(message.data(using: .utf8)!) else { + XCTFail() + return + } + + var decryptionError: Unmanaged? + guard let plainTextData = SecKeyCreateDecryptedData(privateKeyBob2048!, .rsaEncryptionPKCS1, cipherText as CFData, &decryptionError) else { XCTFail() return } @@ -69,15 +135,45 @@ class RSAEncrypterTests: CryptoTestCase { } func testPlainTextTooLong() { - guard publicKey2048 != nil else { + guard publicKeyAlice2048 != nil else { XCTFail() return } - let encrypter = RSAEncrypter(algorithm: .RSA1_5, publicKey: publicKey2048!) + let encrypter = RSAEncrypter(algorithm: .RSA1_5, publicKey: publicKeyAlice2048!) XCTAssertThrowsError(try encrypter.encrypt(Data(count:300))) { (error: Error) in XCTAssertEqual(error as? RSAError, RSAError.plainTextLengthNotSatisfied) } } + func testMaximumPlainTextLength() { + guard publicKeyAlice2048 != nil else { + XCTFail() + return + } + + // RSAES-PKCS1-v1_5 can operate on messages of length up to k - 11 octets (k = octet length of the RSA modulus) + // See https://tools.ietf.org/html/rfc3447#section-7.2 + let maxMessageLengthInBytes = SecKeyGetBlockSize(publicKeyAlice2048!) - 11 + let testMessage = Data(count: maxMessageLengthInBytes) + + let encrypter = RSAEncrypter(algorithm: .RSA1_5, publicKey: publicKeyAlice2048!) + XCTAssertNoThrow(try encrypter.encrypt(testMessage)) + } + + func testMaximumPlainTextLengthPlusOne() { + guard publicKeyAlice2048 != nil else { + XCTFail() + return + } + + let maxMessageLengthInBytes = SecKeyGetBlockSize(publicKeyAlice2048!) - 11 + let testMessage = Data(count: maxMessageLengthInBytes + 1) + + let encrypter = RSAEncrypter(algorithm: .RSA1_5, publicKey: publicKeyAlice2048!) + XCTAssertThrowsError(try encrypter.encrypt(testMessage)) { (error: Error) in + XCTAssertEqual(error as? RSAError, RSAError.plainTextLengthNotSatisfied) + } + } + } diff --git a/Tests/RSAPublicKeyToDataTests.swift b/Tests/RSAPublicKeyToDataTests.swift index c02ff41b..2711dd94 100644 --- a/Tests/RSAPublicKeyToDataTests.swift +++ b/Tests/RSAPublicKeyToDataTests.swift @@ -26,11 +26,11 @@ import XCTest class RSAPublicKeyToDataTests: CryptoTestCase { - func testPublicKey2048ToData() { + func testpublicKeyAlice2048ToData() { let jwk = RSAPublicKey(modulus: expectedModulus2048Base64, exponent: expectedExponentBase64) let data = try! jwk.converted(to: Data.self) - XCTAssertEqual(data, publicKey2048Data) + XCTAssertEqual(data, publicKeyAlice2048Data) } func testPublicKey4096ToData() { diff --git a/Tests/RSAPublicKeyToSecKeyTests.swift b/Tests/RSAPublicKeyToSecKeyTests.swift index 31f1a913..d10685a9 100644 --- a/Tests/RSAPublicKeyToSecKeyTests.swift +++ b/Tests/RSAPublicKeyToSecKeyTests.swift @@ -26,11 +26,11 @@ import XCTest class RSAPublicKeyToSecKeyTests: CryptoTestCase { - func testPublicKey2048ToSecKey() { + func testpublicKeyAlice2048ToSecKey() { let jwk = RSAPublicKey(modulus: expectedModulus2048Base64, exponent: expectedExponentBase64) let key = try! jwk.converted(to: SecKey.self) - XCTAssertEqual(SecKeyCopyExternalRepresentation(key, nil)! as Data, publicKey2048Data) + XCTAssertEqual(SecKeyCopyExternalRepresentation(key, nil)! as Data, publicKeyAlice2048Data) } func testPublicKey4096ToSecKey() { diff --git a/Tests/RSASignerTests.swift b/Tests/RSASignerTests.swift index 7914ac3c..06839ba3 100644 --- a/Tests/RSASignerTests.swift +++ b/Tests/RSASignerTests.swift @@ -36,12 +36,12 @@ class RSASignerTests: CryptoTestCase { } func testSigning() { - guard privateKey2048 != nil else { + guard privateKeyAlice2048 != nil else { XCTFail() return } - let signer = RSASigner(algorithm: .RS512, privateKey: privateKey2048!) + let signer = RSASigner(algorithm: .RS512, privateKey: privateKeyAlice2048!) let signature = try! signer.sign(message.data(using: .utf8)!) XCTAssertEqual(signature.base64URLEncodedString(), signatureBase64URL) diff --git a/Tests/RSAVerifierTests.swift b/Tests/RSAVerifierTests.swift index 59e692af..e717cc1a 100644 --- a/Tests/RSAVerifierTests.swift +++ b/Tests/RSAVerifierTests.swift @@ -35,13 +35,13 @@ class RSAVerifierTests: CryptoTestCase { } func testVerifying() { - guard publicKey2048 != nil else { + guard publicKeyAlice2048 != nil else { XCTFail() return } let jws = try! JWS(compactSerialization: compactSerializedJWSRS512Const) - let verifier = RSAVerifier(algorithm: .RS512, publicKey: publicKey2048!) + let verifier = RSAVerifier(algorithm: .RS512, publicKey: publicKeyAlice2048!) guard let signingInput = [jws.header, jws.payload].asJOSESigningInput() else { XCTFail() diff --git a/Tests/SecKeyRSAPublicKeyTests.swift b/Tests/SecKeyRSAPublicKeyTests.swift index 16bed21e..5e57b373 100644 --- a/Tests/SecKeyRSAPublicKeyTests.swift +++ b/Tests/SecKeyRSAPublicKeyTests.swift @@ -26,8 +26,8 @@ import XCTest class SecKeyRSAPublicKeyTests: CryptoTestCase { - func testPublicKey2048Modulus() { - let components = try? publicKey2048!.rsaPublicKeyComponents() + func testpublicKeyAlice2048Modulus() { + let components = try? publicKeyAlice2048!.rsaPublicKeyComponents() XCTAssertNotNil(components) @@ -36,8 +36,8 @@ class SecKeyRSAPublicKeyTests: CryptoTestCase { XCTAssertEqual(modulus, expectedModulus2048Data) } - func testPublicKey2048Exponent() { - let components = try? publicKey2048!.rsaPublicKeyComponents() + func testpublicKeyAlice2048Exponent() { + let components = try? publicKeyAlice2048!.rsaPublicKeyComponents() XCTAssertNotNil(components) @@ -67,13 +67,13 @@ class SecKeyRSAPublicKeyTests: CryptoTestCase { } func testPrivateKeyToPublicComponents() { - XCTAssertThrowsError(try privateKey2048!.rsaPublicKeyComponents()) { error in + XCTAssertThrowsError(try privateKeyAlice2048!.rsaPublicKeyComponents()) { error in XCTAssertEqual(error as? JWKError, JWKError.notAPublicKey) } } - func testJWKFromPublicKey2048() { - let jwk = try? RSAPublicKey(publicKey: publicKey2048!) + func testJWKFrompublicKeyAlice2048() { + let jwk = try? RSAPublicKey(publicKey: publicKeyAlice2048!) XCTAssertNotNil(jwk) @@ -90,7 +90,7 @@ class SecKeyRSAPublicKeyTests: CryptoTestCase { XCTAssertEqual(jwk!.exponent, expectedExponentBase64) } - func testPublicKey2048FromPublicComponents() { + func testpublicKeyAlice2048FromPublicComponents() { let components = (expectedModulus2048Data, expectedExponentData) guard let secKey = try? SecKey.representing(rsaPublicKeyComponents: components) else { XCTFail() @@ -98,7 +98,7 @@ class SecKeyRSAPublicKeyTests: CryptoTestCase { } let data = SecKeyCopyExternalRepresentation(secKey, nil)! as Data - let dataExpected = SecKeyCopyExternalRepresentation(publicKey2048!, nil)! as Data + let dataExpected = SecKeyCopyExternalRepresentation(publicKeyAlice2048!, nil)! as Data XCTAssertEqual(data, dataExpected) } diff --git a/Tests/SymmetricKeyTests.swift b/Tests/SymmetricKeyTests.swift new file mode 100644 index 00000000..f2a28e25 --- /dev/null +++ b/Tests/SymmetricKeyTests.swift @@ -0,0 +1,126 @@ +// +// SymmetricKeyTests.swift +// Tests +// +// Created by Daniel Egger on 10.07.18. +// +// --------------------------------------------------------------------------- +// Copyright 2018 Airside Mobile Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// --------------------------------------------------------------------------- +// + +import XCTest +@testable import JOSESwift + + +class SymmetricKeyTests: XCTestCase { + + func testCreatingSymmetricKeyFromData() { + // Example key data from https://tools.ietf.org/html/rfc7517#appendix-A.3 but with different "alg" parameter + // because we don't (yet) support "A128KW". + let key = Data(bytes: [ + 0x19, 0xac, 0x20, 0x82, 0xe1, 0x72, 0x1a, 0xb5, 0x8a, 0x6a, 0xfe, 0xc0, 0x5f, 0x85, 0x4a, 0x52 + ]) + + let jwk = SymmetricKey( + key: key, + additionalParameters: [ "alg": SymmetricKeyAlgorithm.A256CBCHS512.rawValue ] + ) + + XCTAssertEqual(jwk.key, "GawgguFyGrWKav7AX4VKUg") + XCTAssertEqual(jwk.keyType, .OCT) + XCTAssertEqual(jwk["alg"], "A256CBC-HS512") + + XCTAssertEqual( + "{\"kty\":\"oct\",\"alg\":\"A256CBC-HS512\",\"k\":\"GawgguFyGrWKav7AX4VKUg\"}", + jwk.jsonString()! + ) + } + + func testParsingSymmetricKeyFromJSONData() { + let key = Data(bytes: [ + 0x19, 0xac, 0x20, 0x82, 0xe1, 0x72, 0x1a, 0xb5, 0x8a, 0x6a, 0xfe, 0xc0, 0x5f, 0x85, 0x4a, 0x52 + ]) + + let json = SymmetricKey( + key: key, + additionalParameters: [ "alg": SymmetricKeyAlgorithm.A256CBCHS512.rawValue ] + ).jsonData()! + + let jwk = try! SymmetricKey(data: json) + + XCTAssertEqual(jwk.key, "GawgguFyGrWKav7AX4VKUg") + XCTAssertEqual(jwk.keyType, .OCT) + XCTAssertEqual(jwk["alg"], "A256CBC-HS512") + + XCTAssertEqual( + "{\"kty\":\"oct\",\"alg\":\"A256CBC-HS512\",\"k\":\"GawgguFyGrWKav7AX4VKUg\"}", + jwk.jsonString()! + ) + } + + func testParsingSymmetricKeyFromOtherKeyRepresentation() { + let key: ExpressibleAsSymmetricKeyComponents = Data(bytes: [ + 0x19, 0xac, 0x20, 0x82, 0xe1, 0x72, 0x1a, 0xb5, 0x8a, 0x6a, 0xfe, 0xc0, 0x5f, 0x85, 0x4a, 0x52 + ]) + + let json = try! SymmetricKey( + key: key, + additionalParameters: [ "alg": SymmetricKeyAlgorithm.A256CBCHS512.rawValue ] + ).jsonData()! + + let jwk = try! SymmetricKey(data: json) + + XCTAssertEqual(jwk.key, "GawgguFyGrWKav7AX4VKUg") + XCTAssertEqual(jwk.keyType, .OCT) + XCTAssertEqual(jwk["alg"], "A256CBC-HS512") + + XCTAssertEqual( + "{\"kty\":\"oct\",\"alg\":\"A256CBC-HS512\",\"k\":\"GawgguFyGrWKav7AX4VKUg\"}", + jwk.jsonString()! + ) + } + + func testSymmetricKeyToData() { + let key = Data(bytes: [ + 0x19, 0xac, 0x20, 0x82, 0xe1, 0x72, 0x1a, 0xb5, 0x8a, 0x6a, 0xfe, 0xc0, 0x5f, 0x85, 0x4a, 0x52 + ]) + + let jwk = SymmetricKey(key: key) + + let keyData = try! jwk.converted(to: Data.self) + + XCTAssertEqual(keyData, key) + } + + func testMalformedSymmetricKeyToData() { + let json = "{\"kty\":\"oct\",\"alg\":\"A256CBC-HS512\",\"k\":\"+++==notbase64url==---\"}".data(using: .utf8)! + + XCTAssertThrowsError(try SymmetricKey(data: json)) + } + + func testDecodingFromJSONWithMissingKeyType() { + let json = "{\"alg\":\"A256CBC-HS512\",\"k\":\"GawgguFyGrWKav7AX4VKUg\"}".data(using: .utf8)! + + XCTAssertThrowsError(try SymmetricKey(data: json)) + } + + func testDecodingFromJSONWithWrongKeyType() { + let json = "{\"kty\":\"RSA\",\"alg\":\"A256CBC-HS512\",\"k\":\"GawgguFyGrWKav7AX4VKUg\"}".data(using: .utf8)! + + XCTAssertThrowsError(try SymmetricKey(data: json)) + } + +} diff --git a/Tests/TestKey.plist b/Tests/TestKeys.plist similarity index 74% rename from Tests/TestKey.plist rename to Tests/TestKeys.plist index d08aed35..76b40424 100644 --- a/Tests/TestKey.plist +++ b/Tests/TestKeys.plist @@ -2,8 +2,10 @@ - com.airsidemobile.JOSESwift.testPrivateKey2048 + com.airsidemobile.JOSESwift.testprivateKeyAlice2048 MIIEogIBAAKCAQEAiADzxMJ+l/NIVPbqz9eoBenUCCUiNNfZ37c6gUJwWEfJRyGchAe96m4GLr3pzj2A3Io4MSKf9dDWMak6qkR/XYljSjZBbXAhQan2sIB5qyPW7NJ7XpJWHoaHdHwEN9Cj29zL+WtFk6lC1rPDmNPRTmRy0ct4EP4YJ49PMcoKJQKbog79ws1KdDzNGTVVkEgLB4VOlW8A164kaK8+xMUxTqySUtigLTDUMqjQ/81SFgsNnMUqnxp87bKD77olYBia88r8V2YXEx1Jgl8t22gNNh6lkN8BDqlkb/Y2uS+c7vlYIfSH6WYkVsSPsrA+GLLRo/R07FGxvs2M5gZxnmlvewIDAQABAoIBADCFCnJdItWpzNnG9zFsEfz+FQ9M1B2/DfLihuQ7ZCIShiuywYhWzLm4Q8tkJGfYCENlqjNZU3Dabrfr1EqPQlMH4xzEK2ZUFQE8lg4U35MfJ5t4Ydv03/Vm8Cct4UFaVULoS/qw+vL5dSdsnXDFzIuniVDwQmbph4uBdHLiTekytlb0N4SFUZWMdYbYNdmJ1JInxOBlU+oCEV9X7rpUL4MPcccpjGqTeMbfrj0Jz/63T7goD3Ti7OXtsfv9TvA8MLde3NS8R0oL8EM7yRFCzpEk5BTGYVBouBNAJwNoJC2yP70dIti419PgzWdkOs7wWj8F63rDfccKm9vWEUFbxRkCgYEAvLn+WftqoWA6EsQ+FfXbHjIhV+1steKgkUXijw6gtt69g6yVMcHpAXLLd13jguC3gDCYrfEM0b80DsisC0p2m7kbH3UIIruUnXxYxNqw+gCix2d0RuiKPQ3vx2VnTZgh7dtF7Sz9TC63dSE+xoNcrhmO5AzIAicVIYGvnJTL4bMCgYEAuHvNn/oSTAWEz2Uf10Dzi6c64aMmlLJy22b6PBK4QG+mTQmR53C1CHq2/73o/ONxtoVl3C+KgmncKwpMjU8We0ohTQEDTKv7uUlmqBpjG1V2NF6G4d1DxLmAhahgAYkKD9NSVzPIO+yLurMPw63Zphcl6umnwfh/OhlMi1gZhxkCgYB7aMhZQN12Tz1KXkcXByDUuwUwwRGwUlSbCm7fCzquujKE8wrQcbOS/eTs1llakOWNjrmYLKMsWPKKpFBURcoPhFinFllOlQjWfqRxfWvy3w2ShST05UTYLc/YvIdzpwKwzg0Izb2I3peaoTWyi93D/vSATZdQSQw5T9ts8aPsnwKBgBHDKcsrYrObHGxzihtJj6l0koDDGqXagKCLS7CZBNB/b32fXELyYRvN5Oy+tj4TEBHIykPm9+kSlDY4qaI5aSq5uncVj+HD9VqjrJSm5b/t/JGSQF5i1XGNgshbq9K6BRP8/sKSo8bRQaraLrxicsBBHk99678LVASeBvarptmRAoGAUq+xPNmhcd7+vP3BslE5JSv0eA14Eyd39Tg85KvsT+zzLDlaWNO2MuvwjeKrvzjX+maPkwUonYd9/jKpdpphk+t8cF5VRM2MA3mCS80nXZXgssRzIF37dvZgZ6pYIKFNmKo/5VQL0z9hAjjKJAc1zb4PPZ19c7CS5CFqg/gZ9dI= + com.airsidemobile.JOSESwift.testprivateKeyBob2048 + MIIEowIBAAKCAQEAsQ7PgCbJbhcuTO/ah0Zmf81I7+sGPhgScbzaHMBO88IXaNcIlfqOcUbymUUp5UaexoLOpphiuetq08l7X6awUeY/YfgtPVpehdFg8gx/tzc0Nm8CT6OCeptiJzEJXoOuuRLuicVcjQP1pdL73Si+l334dQyk62CRi5rxiTZZncl4DxMOZI/nkfTYoqb+Y8ncRUiDMozjaS/OYihjyouwqDGJmbFiImfCHylc3fc/31uuAszBH+x4DjDOnSjkrU2h6fMJ27fA5W2/BI1tEk2hR5xsWEb1fcy1rSJ/xoXQcqWmWLpzJTGbvWLwnWjldHK9HuLpPObZyhXQrP5fj0GkxwIDAQABAoIBABwwYXTjt/ohEqwJG9+ho7wBMPWRk4LbTkRxbsBZFbjVU+jLI4DNTVRXmVQxoMPyBGFSfhgj9sPm6TEiyNOntDa1062DV6CG/LmNZkPjHYpL7bIk5ldLjwB6MJxQwE8d4/lvPC8PDvcchEWMW7EQRZfU5HOuOauHymRRnNbdolLudqHo+aePvFgJN5nAsTp2EV7vPILASJDkKu4lm5DGx+7AfRXTsNZfPFxf/G8cqkjJmukEV9gaK63iT64ssMqWvE+RweD2q4UbxIo1TtXCBN6SRbewsPfVxUtPSi4XaJZTMgDSQfTUasFHB5PHBaJLuQca+5rJwWgKeKZ6kLAIOcECgYEA3208xZ/rxkoEBt3p6cjziyIenNDuCWgHRCK25T40A9GhPQh/MOkJjPcREG7gZhhIvOHhaHbcIZr7zhPp7Pdedk3n2uMGly+kjAXqXVnHuIgcfNCBS+brl/65dFLw4L9e8Kqom1UDtk0QPPMK3qrjEU1LP/+Teme+/7FsMbws9JcCgYEAyt79Au2IHPfq8JwmdaM+10VmXkKvzC/a9tUxXc8R1KVJZXy29djxZpR7mNvNW64mIsXD0tWrHGYB4aiw4qDL6m/H0sy2J/+Bz3sItaaK2ZIhd9lEl18ReLq2cPGz4hC2I/DMiV9CXmeYBGpdZy2hpcNDfO9FPE3IGlHOjlTM51ECgYEAo2CqxsxpSWdISIkvii/Sv1Pk1MEGv0QdxBoqabmxqXU6FXpIH8jYngbwqHabiqyJS+1UueHomH5PUWNIdGpy3cmcGHhYkmdZSLbuqPKE1BBb3H1PfuRONKgkjsYzs/TchNoWTve8riruC9OhiC+nuTgvJY5LFoAUr77aoge7S+MCgYACAYJu7PpTjv/7sQ+QSjTs2/rPI8mpMy/vENyUGOAMQjYfksQcWjDI2t79++5j4azu2xW8l/BBL6EFIi3dj8l0X/aR1wRWEOivrH/BjAwDarZ6AOP0uzPpBa+YHkuxCIlEgbUEBrUZSdzu7j5OLt1STkP0kEkW943q9LQeJCiCgQKBgGKHAex6+n/B2Y+KCzjwHbmqkjZEKbi4oXUPtAYfsrTliiB93IZWyKVCHjzOtUxFYPwLaoQuPBME7mcEXUC6SwY/ikF5HDxygASkZ8tSEIryxKvtl6yjoxEIW36JN0sBPHpycV77JmbM42Ljlbq1GHYwxDBYY0r4kG4cJ+p3GQ3n com.airsidemobile.JOSESwift.testPrivateKey4096 MIIJKgIBAAKCAgEAv+rKTWfCkZmUjQsppCM7u3DVz2bYaoFp/c5r4lwLJXvP9S99dAVMG5YHiJAHVLSMDIm0O5WTNR/1pvwPA57zal2Gss9q+a4imx+f5pyC8e2vtrozS3hejcZyYSSdotJCSfGWaSh1/8CyIyrAoMfHLt4+YHH7U6N1h7nqIzt5thybBObsBkTiul5hMqxf02SEqxZpPfv0AKMKPontcxuO1DRgQUkcPkljKSysurNwmET3Dl50NkuYhCsUe5mz5yu9GHT6HER+47helljRF4d1d4RTkzk1BnXy1ZbmsdTN9vewmYuAqACLAVdPiK4ejw46l8aSOclazireRQ04ismklfP1wc3CJ532CZ4PKf0tfflOmhEAXyjF3VwQsj0yl/4S5JphoutWkq/hN1AVX6K3F9xEENm4dddaetFYWgQpjjth8UwM/Svm7aflR9f+g/1xjxvaPAG+ir+NqIsFeVpk8OJ+ZKg1YMKmYIcyRptnn1XNdJz5724r1xge/oPzfFxYqGEeAmRhPaB5HgvS8ysF0YhtskJFGsB6P3DxFm+1RgUg2IlwBa+8gGvkwZ5W0Hy1gyZo+BZVHG2G+un2nJd/t4L+io1bgBG3sE+vOmV98L6tTzlKdyroa9xi45QRiN0SzA0an0x1fdJzSkh+FcVIQ0ELDJDgruI/mX4M/u3yazsCAwEAAQKCAgAjwEjEZ6ZaujnullxA++FGNyxVUQrSuF32zmvRhKT87BYeziR4Jv55CxdHLNF4yZNnSIW/Lct484WvADiJy6P7tgWALNx4O6StshP3AUPxbn4zs3cXwYoXrhajmw8UhEed/7etykBFDvAAVhgBO4YS6IrFK6aOe55K1OrVemBfeO+115CzUETORAOmTW8icZC3UFr1lYhsT/L6GHaX4VPXmxQtRUHSJpim1HAu7Xt1mi9NyXGrpaR5Ro8vwT++uiqlMjFfb7EVmcRhDJWS5QSQaaGqBNeoZvOAL3a1PPfLYHPQ/qhOY5+BdfLWet0teBCZMoAN7MESM/Eo+uAEFrEmfVHFaTt7ehm3iKKw85ZqO/w29z/g2Ace+doH08mKx2QlZAzwjNqMzvYAs4S2SoEFzeeTFsGRr3CvT9mRjH/45LjNVGAARXTLkdf8BjQEb3LU7/JhQwSOmD+yQF2Zsne3a6q5mYoOYszgoWOSbTTthRUaICWmqbtBtFuy2t+GjKmpo+tylz4yFef9FC8ZoehFrugLk1EGdTGRan5IOHJzr9GXC/dDWprWPlRcSQsX8x0GSxBAHjlHTomTdrXFIob/SUBqPkmSl+HH11zFUITV8DIg+ZTAuUo0WTDToBu7aTGuN+WxxJPIVlYUROagaHgUegW/ACXccN2icsIiMbmypQKCAQEA6hCNaj9/3Xj0K5afKkPLyfEQWGrMadIb1FuzB+6suVgkQaGcPb3MGrOhGEssIdVjq6taeGW0wi+rjFCKazgrOf0SU/zLfy+b6pXSl6s83ETN70JlkQULSATS5KOaU2+48nrZ//5ZtZYgJR6F8aiNYMn5OvqEprMBpWLF2nDJWBkhXhS4YSeLmDNUb+mLJnaEolcS+OkAtGv/lR2W1zRV9Bm2mpgnSr4/qsoratR1racT8k/DuKGBOMHoPD7c5KY0myEkKH1dCdwDK1/WBRTolk2tzQqzDejXRn/Pa51m8dGMp+ORdSACds596YiZQFnW2a1XUA91b/F1jdZDaGHmJwKCAQEA0ecTl9c6th2JJY1HXwp+ysc2Vqw9HvhOYSJozSAiUEDns4ymYeApSJe/2fdEfol7F8A1KYrbpnQiCJcCpSAeTpOphpqDUX/jIAgCSOlG04Vh5/yEE4Vt9j56fxQ7b6W85NhEOifpFC/6onf8/toi6Qmp2EncbFJLSqJR9bGDohkrMod69fIsfTcT/jCO130pCb3JlXxQmpTWLyRvLBRw/Ryrp/uFEcCznnX8+OdEppY16hO09XzjFMtn0+MNlOyTefurOHLiBxsaVbtQ2TBFCmBBjFZdP7wphR5fV/5L+/x4nj34amYfX/8evNF/Q6JWrYCvDx+oNeGpQu9NeAqyzQKCAQEAvMyzNPOZcQzCb3JkWpQrCm4fUCJ9VhI7jnSVGZ3kxvAY9rOkMo0o2JTyKYVhSL02U1+5bwoi3svS5TCGJmw9Od4vbX2oq6O8cWF+aAag5c++CPMWt/5IlkQF1JU+w3SHy1dbBsmklPtOXIKdaksaFutz1KYEX+5eMohUrZlyX1SN/LRb8UoTqKYmwsqwCktpOwVfcVWI0v+WPjKK3HYDqw0epN58OybwdhPzJ43Oik8NoNPEPOZRNHX8nLPcO7bxEfyOjPZqGKzWBaOdH6Y44hwwSAcMXPjmxKANsHFbe00a1sWSK9axBiIToE8lxqlwy06t8g82HCC5QLqGzujA2wKCAQEAqgzQ3MSrdfr7rwaD+cVg7xrxNMqZQcvcg+kCipwr8GSE0ftyl/yotXzrGsSuIu1yYc1/xEYQqQuQvvDIXcplgzcxsjDfjllTQhH69+K1VIvMw8rZnY8NhN8dEnLrrAnTvHpQ2+SVeZtdK96Vgkpq2ezQQr+j6cXJ5Y8XYzsK73oAexEzVql5zX+fnODt+DoX0yKEe6yZCILn+o1D26w71XMyxgaIydcaLZhc97E09tjemOKaSf+BYMlhNlHOIQaAe+Sd1D+OSj902FvGpg3rAPr2X6FgLlBcOtJeX8VO/biOZ3TBGkHdzUG8PzBiMAMGwc1tBY2aEvzcybNhSY0xSQKCAQEAlvlFt7ScgvrmHXu2wvmFPfMu0UCoPTHtY/Ou4QnPcuMlsTSJfcT+bzvGZjohFaZ0m0oZwzTUIsIU4YA43iUx45wH2hPYP0+FShEi4xm4s/mUjOg/0d3lh8K0VOTnR7aPYBdqTwO+/9qpvz92+rVT8llIKN4Opa5uSjg6s5xN0H8SZaOnw/Y2Vob8Trah/uVmAbOGFdMIBdr7hooX4MiYz3QV4FTnu5Zb0SItr6kAdXo49+fO1gmqvmYO2SP1DCbwBVsEb+fW2PVGYoZhWrRY1Ac695A5UsYKanu0FuiQ6IHOZ6OzHh8nCCS4qRPPvEmJRMstgIYzYoCRA4ZDYLXfdw==