diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 761522b..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "Carthage/Checkouts/CryptoSwift"] - path = Carthage/Checkouts/CryptoSwift - url = https://github.com/krzyzanowskim/CryptoSwift.git diff --git a/.travis.yml b/.travis.yml index 41fcf91..2c00dc4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,38 @@ os: -- linux -- osx + - osx + - linux +osx_image: xcode9.4 language: generic sudo: required dist: trusty -osx_image: xcode8.3 + env: -- SWIFT_VERSION=3.1.1 + global: + - SWIFT_VERSION=4.0 + - SWIFT_VERSION=4.1.2 + matrix: + - SWIFTPM_TEST=true + - XCODE_TEST_SDK=macosx + - XCODE_BUILD_SDK=iphonesimulator + - XCODE_BUILD_SDK=appletvsimulator + - XCODE_BUILD_SDK=watchsimulator + +matrix: + exclude: + - os: linux + env: XCODE_TEST_SDK=macosx + - os: linux + env: XCODE_BUILD_SDK=iphonesimulator + - os: linux + env: XCODE_BUILD_SDK=appletvsimulator + - os: linux + env: XCODE_BUILD_SDK=watchsimulator + install: -- if [[ "$TRAVIS_OS_NAME" != "osx" ]]; then eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/9f442512a46d7a2af7b850d65a7e9bd31edfb09b/swiftenv-install.sh)"; fi -- git submodule update --init --recursive + - eval "$(curl -sL https://swiftenv.fuller.li/install.sh)" + script: -- swift test -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then xcodebuild -project JWT.xcodeproj -scheme JWT-OSX test -sdk macosx; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then xcodebuild -project JWT.xcodeproj -scheme JWT-iOS build -sdk iphonesimulator; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then xcodebuild -project JWT.xcodeproj -scheme JWT-tvOS build -sdk appletvsimulator; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then xcodebuild -project JWT.xcodeproj -scheme JWT-watchOS build -sdk watchsimulator; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pod lib lint; fi +- if [ -n "$SWIFTPM_TEST" ]; then swift test; fi +- if [ -n "$XCODE_BUILD_SDK" ] || [ -n "$XCODE_TEST_SDK" ]; then swift package generate-xcodeproj; fi +- if [ -n "$XCODE_BUILD_SDK" ]; then xcodebuild -project JWT.xcodeproj -scheme JWT-Package build -sdk $XCODE_BUILD_SDK; fi +- if [ -n "$XCODE_TEST_SDK" ]; then xcodebuild -project JWT.xcodeproj -scheme JWT-Package test -sdk $XCODE_TEST_SDK; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index bd856eb..2b9686c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,12 @@ # JSON Web Token Changelog -## Master +## 2.2.0 ### Enhancements +- On Apple platforms, JSONWebToken will use the system CommonCrypto where possible. - Allow passing additional headers when encoding a JWT. +- Allow passing leeway parameter for date checks when verifying a JWT. ## 2.1.0 diff --git a/Cartfile b/Cartfile deleted file mode 100644 index 4f0cac5..0000000 --- a/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "krzyzanowskim/CryptoSwift" ~> 0.6.1 diff --git a/Cartfile.resolved b/Cartfile.resolved deleted file mode 100644 index 5b562e4..0000000 --- a/Cartfile.resolved +++ /dev/null @@ -1 +0,0 @@ -github "krzyzanowskim/CryptoSwift" "0.6.1" diff --git a/Carthage/Checkouts/CryptoSwift b/Carthage/Checkouts/CryptoSwift deleted file mode 160000 index 5f9bb95..0000000 --- a/Carthage/Checkouts/CryptoSwift +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5f9bb95c6f246c7e19bf4346a6ad1a0c406415f0 diff --git a/CommonCrypto/module.modulemap b/CommonCrypto/module.modulemap new file mode 100644 index 0000000..b70f7d6 --- /dev/null +++ b/CommonCrypto/module.modulemap @@ -0,0 +1,4 @@ +module CommonCrypto [system] { + header "shim.h" + export * +} diff --git a/CommonCrypto/shim.h b/CommonCrypto/shim.h new file mode 100644 index 0000000..c332624 --- /dev/null +++ b/CommonCrypto/shim.h @@ -0,0 +1 @@ +#include diff --git a/JSONWebToken.podspec b/JSONWebToken.podspec deleted file mode 100644 index 2bf5306..0000000 --- a/JSONWebToken.podspec +++ /dev/null @@ -1,17 +0,0 @@ -Pod::Spec.new do |spec| - spec.name = 'JSONWebToken' - spec.version = '2.1.1' - spec.summary = 'Swift library for JSON Web Tokens (JWT).' - spec.homepage = 'https://github.com/kylef/JSONWebToken.swift' - spec.license = { :type => 'BSD', :file => 'LICENSE' } - spec.author = { 'Kyle Fuller' => 'kyle@fuller.li' } - spec.source = { :git => 'https://github.com/kylef/JSONWebToken.swift.git', :tag => "#{spec.version}" } - spec.source_files = 'Sources/*.swift' - spec.ios.deployment_target = '8.0' - spec.osx.deployment_target = '10.9' - spec.tvos.deployment_target = '9.0' - spec.watchos.deployment_target = '2.0' - spec.requires_arc = true - spec.dependency 'CryptoSwift', '~> 0.6.1' - spec.module_name = 'JWT' -end diff --git a/JWT.xcodeproj/project.pbxproj b/JWT.xcodeproj/project.pbxproj deleted file mode 100644 index 4158229..0000000 --- a/JWT.xcodeproj/project.pbxproj +++ /dev/null @@ -1,892 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 2734C6A81D88001F00BFF9F1 /* CryptoSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66725DAB1C59202E00FC32F4 /* CryptoSwift.framework */; }; - 2734C6A91D88002900BFF9F1 /* CryptoSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66725DAB1C59202E00FC32F4 /* CryptoSwift.framework */; }; - 2734C6AA1D88003000BFF9F1 /* CryptoSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66725DAB1C59202E00FC32F4 /* CryptoSwift.framework */; }; - 277794051DF221F800573F3E /* ClaimSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277794041DF221F800573F3E /* ClaimSet.swift */; }; - 277794061DF221F800573F3E /* ClaimSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277794041DF221F800573F3E /* ClaimSet.swift */; }; - 277794071DF221F800573F3E /* ClaimSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277794041DF221F800573F3E /* ClaimSet.swift */; }; - 277794081DF221F800573F3E /* ClaimSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 277794041DF221F800573F3E /* ClaimSet.swift */; }; - 2777940B1DF22BE400573F3E /* JOSEHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2777940A1DF22BE400573F3E /* JOSEHeader.swift */; }; - 2777940C1DF22BE400573F3E /* JOSEHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2777940A1DF22BE400573F3E /* JOSEHeader.swift */; }; - 2777940D1DF22BE400573F3E /* JOSEHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2777940A1DF22BE400573F3E /* JOSEHeader.swift */; }; - 2777940E1DF22BE400573F3E /* JOSEHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2777940A1DF22BE400573F3E /* JOSEHeader.swift */; }; - 277794101DF22D0D00573F3E /* Encode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2777940F1DF22D0D00573F3E /* Encode.swift */; }; - 277794111DF22D0D00573F3E /* Encode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2777940F1DF22D0D00573F3E /* Encode.swift */; }; - 277794121DF22D0D00573F3E /* Encode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2777940F1DF22D0D00573F3E /* Encode.swift */; }; - 277794131DF22D0D00573F3E /* Encode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2777940F1DF22D0D00573F3E /* Encode.swift */; }; - 279D63A21AD07FFF0024E2BC /* JWT.h in Headers */ = {isa = PBXBuildFile; fileRef = 279D63A11AD07FFF0024E2BC /* JWT.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 279D63A81AD07FFF0024E2BC /* JWT.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 279D639C1AD07FFF0024E2BC /* JWT.framework */; }; - 279D63AF1AD07FFF0024E2BC /* JWTTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279D63AE1AD07FFF0024E2BC /* JWTTests.swift */; }; - 520A71171C469F010005C709 /* Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71131C469F010005C709 /* Base64.swift */; }; - 520A71181C469F010005C709 /* Claims.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71141C469F010005C709 /* Claims.swift */; }; - 520A71191C469F010005C709 /* Decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71151C469F010005C709 /* Decode.swift */; }; - 520A711A1C469F010005C709 /* JWT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71161C469F010005C709 /* JWT.swift */; }; - CD9B62171C7753D8005D4844 /* Claims.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71141C469F010005C709 /* Claims.swift */; }; - CD9B62181C7753D8005D4844 /* JWT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71161C469F010005C709 /* JWT.swift */; }; - CD9B62191C7753D8005D4844 /* Decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71151C469F010005C709 /* Decode.swift */; }; - CD9B621A1C7753D8005D4844 /* Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71131C469F010005C709 /* Base64.swift */; }; - CD9B621E1C7753D8005D4844 /* JWT.h in Headers */ = {isa = PBXBuildFile; fileRef = 279D63A11AD07FFF0024E2BC /* JWT.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CD9B62291C7753EC005D4844 /* Claims.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71141C469F010005C709 /* Claims.swift */; }; - CD9B622A1C7753EC005D4844 /* JWT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71161C469F010005C709 /* JWT.swift */; }; - CD9B622B1C7753EC005D4844 /* Decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71151C469F010005C709 /* Decode.swift */; }; - CD9B622C1C7753EC005D4844 /* Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71131C469F010005C709 /* Base64.swift */; }; - CD9B62301C7753EC005D4844 /* JWT.h in Headers */ = {isa = PBXBuildFile; fileRef = 279D63A11AD07FFF0024E2BC /* JWT.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CD9B623B1C7753FB005D4844 /* Claims.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71141C469F010005C709 /* Claims.swift */; }; - CD9B623C1C7753FB005D4844 /* JWT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71161C469F010005C709 /* JWT.swift */; }; - CD9B623D1C7753FB005D4844 /* Decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71151C469F010005C709 /* Decode.swift */; }; - CD9B623E1C7753FB005D4844 /* Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520A71131C469F010005C709 /* Base64.swift */; }; - CD9B62421C7753FB005D4844 /* JWT.h in Headers */ = {isa = PBXBuildFile; fileRef = 279D63A11AD07FFF0024E2BC /* JWT.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CD9B62891C7758BB005D4844 /* CryptoSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66725DAB1C59202E00FC32F4 /* CryptoSwift.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 279D63A91AD07FFF0024E2BC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 279D63931AD07FFF0024E2BC /* Project object */; - proxyType = 1; - remoteGlobalIDString = 279D639B1AD07FFF0024E2BC; - remoteInfo = JWT; - }; - 66725DAA1C59202E00FC32F4 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 66725DA21C59202E00FC32F4 /* CryptoSwift.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 754BE45519693E190098E6F3; - remoteInfo = "CryptoSwift iOS"; - }; - 66725DB21C59202E00FC32F4 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 66725DA21C59202E00FC32F4 /* CryptoSwift.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 754BE46019693E190098E6F3; - remoteInfo = CryptoSwiftTests; - }; - CD9B628A1C7758CA005D4844 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 66725DA21C59202E00FC32F4 /* CryptoSwift.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = 754BE45419693E190098E6F3; - remoteInfo = "CryptoSwift iOS"; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 277794041DF221F800573F3E /* ClaimSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClaimSet.swift; sourceTree = ""; }; - 2777940A1DF22BE400573F3E /* JOSEHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JOSEHeader.swift; sourceTree = ""; }; - 2777940F1DF22D0D00573F3E /* Encode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Encode.swift; sourceTree = ""; }; - 279D639C1AD07FFF0024E2BC /* JWT.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JWT.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 279D63A01AD07FFF0024E2BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 279D63A11AD07FFF0024E2BC /* JWT.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JWT.h; sourceTree = ""; }; - 279D63A71AD07FFF0024E2BC /* JWTTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JWTTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 279D63AD1AD07FFF0024E2BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 279D63AE1AD07FFF0024E2BC /* JWTTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWTTests.swift; sourceTree = ""; }; - 520A71131C469F010005C709 /* Base64.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Base64.swift; sourceTree = ""; }; - 520A71141C469F010005C709 /* Claims.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Claims.swift; sourceTree = ""; }; - 520A71151C469F010005C709 /* Decode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Decode.swift; sourceTree = ""; }; - 520A71161C469F010005C709 /* JWT.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JWT.swift; sourceTree = ""; }; - 520A711B1C469F440005C709 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; - 540942F3614C41E3827F2013 /* Pods_JWT.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_JWT.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 66725DA21C59202E00FC32F4 /* CryptoSwift.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CryptoSwift.xcodeproj; path = Carthage/Checkouts/CryptoSwift/CryptoSwift.xcodeproj; sourceTree = ""; }; - CD9B62231C7753D8005D4844 /* JWT.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JWT.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - CD9B62351C7753EC005D4844 /* JWT.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JWT.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - CD9B62471C7753FB005D4844 /* JWT.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JWT.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - CE8198B6E30BA6B8F8125FA7 /* Pods_JWTTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_JWTTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 279D63981AD07FFF0024E2BC /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 2734C6A81D88001F00BFF9F1 /* CryptoSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 279D63A41AD07FFF0024E2BC /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 279D63A81AD07FFF0024E2BC /* JWT.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CD9B621B1C7753D8005D4844 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - CD9B62891C7758BB005D4844 /* CryptoSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CD9B622D1C7753EC005D4844 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 2734C6A91D88002900BFF9F1 /* CryptoSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CD9B623F1C7753FB005D4844 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 2734C6AA1D88003000BFF9F1 /* CryptoSwift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 279D63921AD07FFF0024E2BC = { - isa = PBXGroup; - children = ( - 66725DA21C59202E00FC32F4 /* CryptoSwift.xcodeproj */, - 520A711B1C469F440005C709 /* Package.swift */, - 520A71121C469F010005C709 /* Sources */, - 279D639E1AD07FFF0024E2BC /* Sources */, - 279D63AB1AD07FFF0024E2BC /* Tests */, - 279D639D1AD07FFF0024E2BC /* Products */, - AC8AE547FDAF3DD80EB4DB2F /* Frameworks */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - }; - 279D639D1AD07FFF0024E2BC /* Products */ = { - isa = PBXGroup; - children = ( - 279D639C1AD07FFF0024E2BC /* JWT.framework */, - 279D63A71AD07FFF0024E2BC /* JWTTests.xctest */, - CD9B62231C7753D8005D4844 /* JWT.framework */, - CD9B62351C7753EC005D4844 /* JWT.framework */, - CD9B62471C7753FB005D4844 /* JWT.framework */, - ); - name = Products; - sourceTree = ""; - }; - 279D639E1AD07FFF0024E2BC /* Sources */ = { - isa = PBXGroup; - children = ( - 279D63A11AD07FFF0024E2BC /* JWT.h */, - 279D639F1AD07FFF0024E2BC /* Supporting Files */, - ); - name = Sources; - path = JWT; - sourceTree = ""; - }; - 279D639F1AD07FFF0024E2BC /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 279D63A01AD07FFF0024E2BC /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 279D63AB1AD07FFF0024E2BC /* Tests */ = { - isa = PBXGroup; - children = ( - 279D63AE1AD07FFF0024E2BC /* JWTTests.swift */, - 279D63AC1AD07FFF0024E2BC /* Supporting Files */, - ); - name = Tests; - path = Tests/JWTTests; - sourceTree = ""; - }; - 279D63AC1AD07FFF0024E2BC /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 279D63AD1AD07FFF0024E2BC /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 520A71121C469F010005C709 /* Sources */ = { - isa = PBXGroup; - children = ( - 277794041DF221F800573F3E /* ClaimSet.swift */, - 2777940A1DF22BE400573F3E /* JOSEHeader.swift */, - 520A71131C469F010005C709 /* Base64.swift */, - 520A71141C469F010005C709 /* Claims.swift */, - 520A71151C469F010005C709 /* Decode.swift */, - 2777940F1DF22D0D00573F3E /* Encode.swift */, - 520A71161C469F010005C709 /* JWT.swift */, - ); - path = Sources; - sourceTree = ""; - }; - 66725DA31C59202E00FC32F4 /* Products */ = { - isa = PBXGroup; - children = ( - 66725DAB1C59202E00FC32F4 /* CryptoSwift.framework */, - 66725DB31C59202E00FC32F4 /* Tests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - AC8AE547FDAF3DD80EB4DB2F /* Frameworks */ = { - isa = PBXGroup; - children = ( - 540942F3614C41E3827F2013 /* Pods_JWT.framework */, - CE8198B6E30BA6B8F8125FA7 /* Pods_JWTTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 279D63991AD07FFF0024E2BC /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 279D63A21AD07FFF0024E2BC /* JWT.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CD9B621D1C7753D8005D4844 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - CD9B621E1C7753D8005D4844 /* JWT.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CD9B622F1C7753EC005D4844 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - CD9B62301C7753EC005D4844 /* JWT.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CD9B62411C7753FB005D4844 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - CD9B62421C7753FB005D4844 /* JWT.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 279D639B1AD07FFF0024E2BC /* JWT-OSX */ = { - isa = PBXNativeTarget; - buildConfigurationList = 279D63B21AD07FFF0024E2BC /* Build configuration list for PBXNativeTarget "JWT-OSX" */; - buildPhases = ( - 279D63971AD07FFF0024E2BC /* Sources */, - 279D63981AD07FFF0024E2BC /* Frameworks */, - 279D63991AD07FFF0024E2BC /* Headers */, - 66725DA11C591D9800FC32F4 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "JWT-OSX"; - productName = JWT; - productReference = 279D639C1AD07FFF0024E2BC /* JWT.framework */; - productType = "com.apple.product-type.framework"; - }; - 279D63A61AD07FFF0024E2BC /* JWTTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 279D63B51AD07FFF0024E2BC /* Build configuration list for PBXNativeTarget "JWTTests" */; - buildPhases = ( - 279D63A31AD07FFF0024E2BC /* Sources */, - 279D63A41AD07FFF0024E2BC /* Frameworks */, - 279D63A51AD07FFF0024E2BC /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 279D63AA1AD07FFF0024E2BC /* PBXTargetDependency */, - ); - name = JWTTests; - productName = JWTTests; - productReference = 279D63A71AD07FFF0024E2BC /* JWTTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - CD9B62131C7753D8005D4844 /* JWT-iOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = CD9B62201C7753D8005D4844 /* Build configuration list for PBXNativeTarget "JWT-iOS" */; - buildPhases = ( - CD9B62161C7753D8005D4844 /* Sources */, - CD9B621B1C7753D8005D4844 /* Frameworks */, - CD9B621D1C7753D8005D4844 /* Headers */, - CD9B621F1C7753D8005D4844 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - CD9B628B1C7758CA005D4844 /* PBXTargetDependency */, - ); - name = "JWT-iOS"; - productName = JWT; - productReference = CD9B62231C7753D8005D4844 /* JWT.framework */; - productType = "com.apple.product-type.framework"; - }; - CD9B62251C7753EC005D4844 /* JWT-tvOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = CD9B62321C7753EC005D4844 /* Build configuration list for PBXNativeTarget "JWT-tvOS" */; - buildPhases = ( - CD9B62281C7753EC005D4844 /* Sources */, - CD9B622D1C7753EC005D4844 /* Frameworks */, - CD9B622F1C7753EC005D4844 /* Headers */, - CD9B62311C7753EC005D4844 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "JWT-tvOS"; - productName = JWT; - productReference = CD9B62351C7753EC005D4844 /* JWT.framework */; - productType = "com.apple.product-type.framework"; - }; - CD9B62371C7753FB005D4844 /* JWT-watchOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = CD9B62441C7753FB005D4844 /* Build configuration list for PBXNativeTarget "JWT-watchOS" */; - buildPhases = ( - CD9B623A1C7753FB005D4844 /* Sources */, - CD9B623F1C7753FB005D4844 /* Frameworks */, - CD9B62411C7753FB005D4844 /* Headers */, - CD9B62431C7753FB005D4844 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "JWT-watchOS"; - productName = JWT; - productReference = CD9B62471C7753FB005D4844 /* JWT.framework */; - productType = "com.apple.product-type.framework"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 279D63931AD07FFF0024E2BC /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftMigration = 0700; - LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0800; - ORGANIZATIONNAME = Cocode; - TargetAttributes = { - 279D639B1AD07FFF0024E2BC = { - CreatedOnToolsVersion = 6.2; - }; - 279D63A61AD07FFF0024E2BC = { - CreatedOnToolsVersion = 6.2; - }; - }; - }; - buildConfigurationList = 279D63961AD07FFF0024E2BC /* Build configuration list for PBXProject "JWT" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - ); - mainGroup = 279D63921AD07FFF0024E2BC; - productRefGroup = 279D639D1AD07FFF0024E2BC /* Products */; - projectDirPath = ""; - projectReferences = ( - { - ProductGroup = 66725DA31C59202E00FC32F4 /* Products */; - ProjectRef = 66725DA21C59202E00FC32F4 /* CryptoSwift.xcodeproj */; - }, - ); - projectRoot = ""; - targets = ( - 279D639B1AD07FFF0024E2BC /* JWT-OSX */, - CD9B62131C7753D8005D4844 /* JWT-iOS */, - CD9B62251C7753EC005D4844 /* JWT-tvOS */, - CD9B62371C7753FB005D4844 /* JWT-watchOS */, - 279D63A61AD07FFF0024E2BC /* JWTTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXReferenceProxy section */ - 66725DAB1C59202E00FC32F4 /* CryptoSwift.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = CryptoSwift.framework; - remoteRef = 66725DAA1C59202E00FC32F4 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 66725DB31C59202E00FC32F4 /* Tests.xctest */ = { - isa = PBXReferenceProxy; - fileType = wrapper.cfbundle; - path = Tests.xctest; - remoteRef = 66725DB21C59202E00FC32F4 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; -/* End PBXReferenceProxy section */ - -/* Begin PBXResourcesBuildPhase section */ - 279D63A51AD07FFF0024E2BC /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 66725DA11C591D9800FC32F4 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CD9B621F1C7753D8005D4844 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CD9B62311C7753EC005D4844 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CD9B62431C7753FB005D4844 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 279D63971AD07FFF0024E2BC /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 520A71181C469F010005C709 /* Claims.swift in Sources */, - 520A711A1C469F010005C709 /* JWT.swift in Sources */, - 520A71191C469F010005C709 /* Decode.swift in Sources */, - 277794101DF22D0D00573F3E /* Encode.swift in Sources */, - 2777940B1DF22BE400573F3E /* JOSEHeader.swift in Sources */, - 277794051DF221F800573F3E /* ClaimSet.swift in Sources */, - 520A71171C469F010005C709 /* Base64.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 279D63A31AD07FFF0024E2BC /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 279D63AF1AD07FFF0024E2BC /* JWTTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CD9B62161C7753D8005D4844 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CD9B62171C7753D8005D4844 /* Claims.swift in Sources */, - CD9B62181C7753D8005D4844 /* JWT.swift in Sources */, - CD9B62191C7753D8005D4844 /* Decode.swift in Sources */, - 277794111DF22D0D00573F3E /* Encode.swift in Sources */, - 2777940C1DF22BE400573F3E /* JOSEHeader.swift in Sources */, - 277794061DF221F800573F3E /* ClaimSet.swift in Sources */, - CD9B621A1C7753D8005D4844 /* Base64.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CD9B62281C7753EC005D4844 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CD9B62291C7753EC005D4844 /* Claims.swift in Sources */, - CD9B622A1C7753EC005D4844 /* JWT.swift in Sources */, - CD9B622B1C7753EC005D4844 /* Decode.swift in Sources */, - 277794121DF22D0D00573F3E /* Encode.swift in Sources */, - 2777940D1DF22BE400573F3E /* JOSEHeader.swift in Sources */, - 277794071DF221F800573F3E /* ClaimSet.swift in Sources */, - CD9B622C1C7753EC005D4844 /* Base64.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CD9B623A1C7753FB005D4844 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CD9B623B1C7753FB005D4844 /* Claims.swift in Sources */, - CD9B623C1C7753FB005D4844 /* JWT.swift in Sources */, - CD9B623D1C7753FB005D4844 /* Decode.swift in Sources */, - 277794131DF22D0D00573F3E /* Encode.swift in Sources */, - 2777940E1DF22BE400573F3E /* JOSEHeader.swift in Sources */, - 277794081DF221F800573F3E /* ClaimSet.swift in Sources */, - CD9B623E1C7753FB005D4844 /* Base64.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 279D63AA1AD07FFF0024E2BC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 279D639B1AD07FFF0024E2BC /* JWT-OSX */; - targetProxy = 279D63A91AD07FFF0024E2BC /* PBXContainerItemProxy */; - }; - CD9B628B1C7758CA005D4844 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = "CryptoSwift iOS"; - targetProxy = CD9B628A1C7758CA005D4844 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 279D63B01AD07FFF0024E2BC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.9; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "$(PROJECT_NAME)"; - SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 279D63B11AD07FFF0024E2BC /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.9; - MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_NAME = "$(PROJECT_NAME)"; - SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 279D63B31AD07FFF0024E2BC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - INFOPLIST_FILE = JWT/Info.plist; - INSTALL_PATH = "@rpath"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocode.$(PRODUCT_NAME:rfc1034identifier)"; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - 279D63B41AD07FFF0024E2BC /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - INFOPLIST_FILE = JWT/Info.plist; - INSTALL_PATH = "@rpath"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocode.$(PRODUCT_NAME:rfc1034identifier)"; - SKIP_INSTALL = YES; - }; - name = Release; - }; - 279D63B61AD07FFF0024E2BC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(DEVELOPER_FRAMEWORKS_DIR)", - "$(inherited)", - ); - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = Tests/JWTTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocode.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 279D63B71AD07FFF0024E2BC /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(DEVELOPER_FRAMEWORKS_DIR)", - "$(inherited)", - ); - INFOPLIST_FILE = Tests/JWTTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocode.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - CD9B62211C7753D8005D4844 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = JWT/Info.plist; - INSTALL_PATH = "@rpath"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocode.$(PRODUCT_NAME:rfc1034identifier)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - CD9B62221C7753D8005D4844 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = JWT/Info.plist; - INSTALL_PATH = "@rpath"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocode.$(PRODUCT_NAME:rfc1034identifier)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - CD9B62331C7753EC005D4844 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = JWT/Info.plist; - INSTALL_PATH = "@rpath"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocode.$(PRODUCT_NAME:rfc1034identifier)"; - SDKROOT = appletvos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.0; - }; - name = Debug; - }; - CD9B62341C7753EC005D4844 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = JWT/Info.plist; - INSTALL_PATH = "@rpath"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocode.$(PRODUCT_NAME:rfc1034identifier)"; - SDKROOT = appletvos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.0; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - CD9B62451C7753FB005D4844 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = JWT/Info.plist; - INSTALL_PATH = "@rpath"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocode.$(PRODUCT_NAME:rfc1034identifier)"; - SDKROOT = watchos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 2.0; - }; - name = Debug; - }; - CD9B62461C7753FB005D4844 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = JWT/Info.plist; - INSTALL_PATH = "@rpath"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocode.$(PRODUCT_NAME:rfc1034identifier)"; - SDKROOT = watchos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = 4; - VALIDATE_PRODUCT = YES; - WATCHOS_DEPLOYMENT_TARGET = 2.0; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 279D63961AD07FFF0024E2BC /* Build configuration list for PBXProject "JWT" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 279D63B01AD07FFF0024E2BC /* Debug */, - 279D63B11AD07FFF0024E2BC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 279D63B21AD07FFF0024E2BC /* Build configuration list for PBXNativeTarget "JWT-OSX" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 279D63B31AD07FFF0024E2BC /* Debug */, - 279D63B41AD07FFF0024E2BC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 279D63B51AD07FFF0024E2BC /* Build configuration list for PBXNativeTarget "JWTTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 279D63B61AD07FFF0024E2BC /* Debug */, - 279D63B71AD07FFF0024E2BC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - CD9B62201C7753D8005D4844 /* Build configuration list for PBXNativeTarget "JWT-iOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CD9B62211C7753D8005D4844 /* Debug */, - CD9B62221C7753D8005D4844 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - CD9B62321C7753EC005D4844 /* Build configuration list for PBXNativeTarget "JWT-tvOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CD9B62331C7753EC005D4844 /* Debug */, - CD9B62341C7753EC005D4844 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - CD9B62441C7753FB005D4844 /* Build configuration list for PBXNativeTarget "JWT-watchOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CD9B62451C7753FB005D4844 /* Debug */, - CD9B62461C7753FB005D4844 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 279D63931AD07FFF0024E2BC /* Project object */; -} diff --git a/JWT.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/JWT.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index e3cefe8..0000000 --- a/JWT.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/JWT.xcodeproj/xcshareddata/xcschemes/JWT-OSX.xcscheme b/JWT.xcodeproj/xcshareddata/xcschemes/JWT-OSX.xcscheme deleted file mode 100644 index ff0db9d..0000000 --- a/JWT.xcodeproj/xcshareddata/xcschemes/JWT-OSX.xcscheme +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/JWT.xcodeproj/xcshareddata/xcschemes/JWT-iOS.xcscheme b/JWT.xcodeproj/xcshareddata/xcschemes/JWT-iOS.xcscheme deleted file mode 100644 index c774f4a..0000000 --- a/JWT.xcodeproj/xcshareddata/xcschemes/JWT-iOS.xcscheme +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/JWT.xcodeproj/xcshareddata/xcschemes/JWT-tvOS.xcscheme b/JWT.xcodeproj/xcshareddata/xcschemes/JWT-tvOS.xcscheme deleted file mode 100644 index 624d99c..0000000 --- a/JWT.xcodeproj/xcshareddata/xcschemes/JWT-tvOS.xcscheme +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/JWT.xcodeproj/xcshareddata/xcschemes/JWT-watchOS.xcscheme b/JWT.xcodeproj/xcshareddata/xcschemes/JWT-watchOS.xcscheme deleted file mode 100644 index 87c365a..0000000 --- a/JWT.xcodeproj/xcshareddata/xcschemes/JWT-watchOS.xcscheme +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/JWT/Info.plist b/JWT/Info.plist deleted file mode 100644 index ba3fdf1..0000000 --- a/JWT/Info.plist +++ /dev/null @@ -1,28 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSHumanReadableCopyright - Copyright © 2015 Cocode. All rights reserved. - NSPrincipalClass - - - diff --git a/JWT/JWT.h b/JWT/JWT.h deleted file mode 100644 index 1af30d5..0000000 --- a/JWT/JWT.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// JWT.h -// JWT -// -// Created by Kyle Fuller on 04/04/2015. -// Copyright (c) 2015 Cocode. All rights reserved. -// - -#import - -//! Project version number for JWT. -FOUNDATION_EXPORT double JWTVersionNumber; - -//! Project version string for JWT. -FOUNDATION_EXPORT const unsigned char JWTVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import diff --git a/Package.swift b/Package.swift index f90189c..01b5e95 100644 --- a/Package.swift +++ b/Package.swift @@ -1,8 +1,37 @@ +// swift-tools-version:4.0 + import PackageDescription + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +#if canImport(CommonCrypto) +let dependencies: [Package.Dependency] = [] +#else +let dependencies = [ + Package.Dependency.package(url: "https://github.com/kylef-archive/CommonCrypto.git", from: "1.0.0"), +] +#endif +let excludes = ["HMAC/HMACCryptoSwift.swift"] +let targetDependencies: [Target.Dependency] = [] +#else +let dependencies = [ + Package.Dependency.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "0.10.0"), +] +let excludes = ["HMAC/HMACCommonCrypto.swift"] +let targetDependencies: [Target.Dependency] = ["CryptoSwift"] +#endif + + let package = Package( name: "JWT", - dependencies: [ - .Package(url: "https://github.com/krzyzanowskim/CryptoSwift", versions: Version(0, 6, 1) ..< Version(0, 7, 0)), + products: [ + .library(name: "JWT", targets: ["JWT"]), + ], + dependencies: dependencies, + targets: [ + .target(name: "JWA", dependencies: targetDependencies, exclude: excludes), + .target(name: "JWT", dependencies: ["JWA"]), + .testTarget(name: "JWATests", dependencies: ["JWA"]), + .testTarget(name: "JWTTests", dependencies: ["JWT"]), ] ) diff --git a/README.md b/README.md index 830f70d..0874ddd 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,14 @@ Swift implementation of [JSON Web Token](https://tools.ietf.org/html/draft-ietf- ## Installation -[CocoaPods](http://cocoapods.org/) is the recommended installation method. +Swift Pacakage Manager is the recommended installation method for JSONWebToken, [CocoaPods](http://cocoapods.org/) is also supported. ```ruby pod 'JSONWebToken' ``` +**NOTE:** *Carthage may be supported, however support will not be provided for this installation method, use at your own risk if you know how it works.* + ## Usage ```swift @@ -32,7 +34,7 @@ claims.issuer = "fuller.li" claims.issuedAt = Date() claims["custom"] = "Hi" -JWT.encode(claims: claims, algorithm, algorithm: .hs256("secret".data(using: .utf8))) +JWT.encode(claims: claims, algorithm: .hs256("secret".data(using: .utf8)!)) ``` #### Building a JWT with the builder pattern @@ -68,6 +70,12 @@ try JWT.decode("eyJh...5w", algorithms: [ ]) ``` +You might also want to give your iat, exp and nbf checks some kind of leeway to account for skewed clocks. You can do this by passing a `leeway` parameter like this: + +```swift +try JWT.decode("eyJh...5w", algorithm: .hs256("secret".data(using: .utf8)!), leeway: 10) +``` + #### Supported claims The library supports validating the following claims: diff --git a/Sources/Base64.swift b/Sources/Base64.swift deleted file mode 100644 index c85719b..0000000 --- a/Sources/Base64.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation - -/// URI Safe base64 encode -func base64encode(_ input: Data) -> String { - let data = input.base64EncodedData(options: NSData.Base64EncodingOptions(rawValue: 0)) - let string = String(data: data, encoding: .utf8)! - return string - .replacingOccurrences(of: "+", with: "-", options: NSString.CompareOptions(rawValue: 0), range: nil) - .replacingOccurrences(of: "/", with: "_", options: NSString.CompareOptions(rawValue: 0), range: nil) - .replacingOccurrences(of: "=", with: "", options: NSString.CompareOptions(rawValue: 0), range: nil) -} - -/// URI Safe base64 decode -func base64decode(_ input: String) -> Data? { - let rem = input.characters.count % 4 - - var ending = "" - if rem > 0 { - let amount = 4 - rem - ending = String(repeating: "=", count: amount) - } - - let base64 = input.replacingOccurrences(of: "-", with: "+", options: NSString.CompareOptions(rawValue: 0), range: nil) - .replacingOccurrences(of: "_", with: "/", options: NSString.CompareOptions(rawValue: 0), range: nil) + ending - - return Data(base64Encoded: base64, options: NSData.Base64DecodingOptions(rawValue: 0)) -} diff --git a/Sources/JOSEHeader.swift b/Sources/JOSEHeader.swift deleted file mode 100644 index e19453c..0000000 --- a/Sources/JOSEHeader.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// JOSEHeader.swift -// JWT -// -// Created by Kyle Fuller on 02/12/2016. -// Copyright © 2016 Cocode. All rights reserved. -// - -import Foundation - - -struct JOSEHeader { - var parameters: [String: Any] - - init(parameters: [String: Any]) { - self.parameters = parameters - } - - var algorithm: String? { - get { - return parameters["alg"] as? String - } - - set { - parameters["alg"] = newValue - } - } -} diff --git a/Sources/JWA/HMAC/HMAC.swift b/Sources/JWA/HMAC/HMAC.swift new file mode 100644 index 0000000..9252a03 --- /dev/null +++ b/Sources/JWA/HMAC/HMAC.swift @@ -0,0 +1,36 @@ +import Foundation + + +final public class HMACAlgorithm: Algorithm { + public let key: Data + public let hash: Hash + + public enum Hash { + case sha256 + case sha384 + case sha512 + } + + public init(key: Data, hash: Hash) { + self.key = key + self.hash = hash + } + + public init?(key: String, hash: Hash) { + guard let key = key.data(using: .utf8) else { return nil } + + self.key = key + self.hash = hash + } + + public var name: String { + switch hash { + case .sha256: + return "HS256" + case .sha384: + return "HS384" + case .sha512: + return "HS512" + } + } +} diff --git a/Sources/JWA/HMAC/HMACCommonCrypto.swift b/Sources/JWA/HMAC/HMACCommonCrypto.swift new file mode 100644 index 0000000..6dffce0 --- /dev/null +++ b/Sources/JWA/HMAC/HMACCommonCrypto.swift @@ -0,0 +1,48 @@ +import Foundation +import CommonCrypto + + +extension HMACAlgorithm: SignAlgorithm, VerifyAlgorithm { + public func sign(_ message: Data) -> Data { + let context = UnsafeMutablePointer.allocate(capacity: 1) + defer { context.deallocate() } + + key.withUnsafeBytes() { (buffer: UnsafePointer) in + CCHmacInit(context, hash.commonCryptoAlgorithm, buffer, size_t(key.count)) + } + + message.withUnsafeBytes { (buffer: UnsafePointer) in + CCHmacUpdate(context, buffer, size_t(message.count)) + } + + var hmac = Array(repeating: 0, count: Int(hash.commonCryptoDigestLength)) + CCHmacFinal(context, &hmac) + + return Data(hmac) + } +} + + +extension HMACAlgorithm.Hash { + var commonCryptoAlgorithm: CCHmacAlgorithm { + switch self { + case .sha256: + return CCHmacAlgorithm(kCCHmacAlgSHA256) + case .sha384: + return CCHmacAlgorithm(kCCHmacAlgSHA384) + case .sha512: + return CCHmacAlgorithm(kCCHmacAlgSHA512) + } + } + + var commonCryptoDigestLength: Int32 { + switch self { + case .sha256: + return CC_SHA256_DIGEST_LENGTH + case .sha384: + return CC_SHA384_DIGEST_LENGTH + case .sha512: + return CC_SHA512_DIGEST_LENGTH + } + } +} diff --git a/Sources/JWA/HMAC/HMACCryptoSwift.swift b/Sources/JWA/HMAC/HMACCryptoSwift.swift new file mode 100644 index 0000000..6153049 --- /dev/null +++ b/Sources/JWA/HMAC/HMACCryptoSwift.swift @@ -0,0 +1,32 @@ +import Foundation +import CryptoSwift + + +extension HMACAlgorithm: SignAlgorithm, VerifyAlgorithm { + public func sign(_ message: Data) -> Data { + let mac = HMAC(key: key.bytes, variant: hash.cryptoSwiftVariant) + + let result: [UInt8] + do { + result = try mac.authenticate(message.bytes) + } catch { + result = [] + } + + return Data(bytes: result) + } +} + + +extension HMACAlgorithm.Hash { + var cryptoSwiftVariant: HMAC.Variant { + switch self { + case .sha256: + return .sha256 + case .sha384: + return .sha384 + case .sha512: + return .sha512 + } + } +} diff --git a/Sources/JWA/JWA.swift b/Sources/JWA/JWA.swift new file mode 100644 index 0000000..2e02f7d --- /dev/null +++ b/Sources/JWA/JWA.swift @@ -0,0 +1,32 @@ +import Foundation + + +/// Represents a JSON Web Algorithm (JWA) +/// https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 +public protocol Algorithm: class { + var name: String { get } +} + + +// MARK: Signing + +/// Represents a JSON Web Algorithm (JWA) that is capable of signing +public protocol SignAlgorithm: Algorithm { + func sign(_ message: Data) -> Data +} + + +// MARK: Verifying + +/// Represents a JSON Web Algorithm (JWA) that is capable of verifying +public protocol VerifyAlgorithm: Algorithm { + func verify(_ message: Data, signature: Data) -> Bool +} + + +extension SignAlgorithm { + /// Verify a signature for a message using the algorithm + public func verify(_ message: Data, signature: Data) -> Bool { + return sign(message) == signature + } +} diff --git a/Sources/JWA/None.swift b/Sources/JWA/None.swift new file mode 100644 index 0000000..26a238d --- /dev/null +++ b/Sources/JWA/None.swift @@ -0,0 +1,15 @@ +import Foundation + + +/// No Algorithm, i-e, insecure +public final class NoneAlgorithm: Algorithm, SignAlgorithm, VerifyAlgorithm { + public var name: String { + return "none" + } + + public init() {} + + public func sign(_ message: Data) -> Data { + return Data() + } +} diff --git a/Sources/JWT.swift b/Sources/JWT.swift deleted file mode 100644 index 3b3e4a3..0000000 --- a/Sources/JWT.swift +++ /dev/null @@ -1,66 +0,0 @@ -import Foundation -import CryptoSwift - -public typealias Payload = [String: Any] - -/// The supported Algorithms -public enum Algorithm: CustomStringConvertible { - /// No Algorithm, i-e, insecure - case none - - /// HMAC using SHA-256 hash algorithm - case hs256(Data) - - /// HMAC using SHA-384 hash algorithm - case hs384(Data) - - /// HMAC using SHA-512 hash algorithm - case hs512(Data) - - public var description: String { - switch self { - case .none: - return "none" - case .hs256: - return "HS256" - case .hs384: - return "HS384" - case .hs512: - return "HS512" - } - } - - /// Sign a message using the algorithm - func sign(_ message: String) -> String { - func signHS(_ key: Data, variant: CryptoSwift.HMAC.Variant) -> String { - let messageData = message.data(using: String.Encoding.utf8, allowLossyConversion: false)! - let mac = HMAC(key: key.bytes, variant: variant) - let result: [UInt8] - do { - result = try mac.authenticate(messageData.bytes) - } catch { - result = [] - } - return base64encode(Data(bytes: result)) - } - - switch self { - case .none: - return "" - - case .hs256(let key): - return signHS(key, variant: .sha256) - - case .hs384(let key): - return signHS(key, variant: .sha384) - - case .hs512(let key): - return signHS(key, variant: .sha512) - } - } - - /// Verify a signature for a message using the algorithm - func verify(_ message: String, signature: Data) -> Bool { - return sign(message) == base64encode(signature) - } -} diff --git a/Sources/JWT/Algorithm.swift b/Sources/JWT/Algorithm.swift new file mode 100644 index 0000000..f1d1e91 --- /dev/null +++ b/Sources/JWT/Algorithm.swift @@ -0,0 +1,36 @@ +import Foundation +import JWA + + +/// Represents a JSON Web Algorithm (JWA) +/// https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 +public enum Algorithm: CustomStringConvertible { + /// No Algorithm, i-e, insecure + case none + + /// HMAC using SHA-256 hash algorithm + case hs256(Data) + + /// HMAC using SHA-384 hash algorithm + case hs384(Data) + + /// HMAC using SHA-512 hash algorithm + case hs512(Data) + + var algorithm: SignAlgorithm { + switch self { + case .none: + return NoneAlgorithm() + case .hs256(let key): + return HMACAlgorithm(key: key, hash: .sha256) + case .hs384(let key): + return HMACAlgorithm(key: key, hash: .sha384) + case .hs512(let key): + return HMACAlgorithm(key: key, hash: .sha512) + } + } + + public var description: String { + return algorithm.name + } +} diff --git a/Sources/JWT/Base64.swift b/Sources/JWT/Base64.swift new file mode 100644 index 0000000..4a93f7f --- /dev/null +++ b/Sources/JWT/Base64.swift @@ -0,0 +1,27 @@ +import Foundation + +/// URI Safe base64 encode +func base64encode(_ input: Data) -> String { + let data = input.base64EncodedData() + let string = String(data: data, encoding: .utf8)! + return string + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "=", with: "") +} + +/// URI Safe base64 decode +func base64decode(_ input: String) -> Data? { + let rem = input.count % 4 + + var ending = "" + if rem > 0 { + let amount = 4 - rem + ending = String(repeating: "=", count: amount) + } + + let base64 = input.replacingOccurrences(of: "-", with: "+") + .replacingOccurrences(of: "_", with: "/") + ending + + return Data(base64Encoded: base64) +} diff --git a/Sources/ClaimSet.swift b/Sources/JWT/ClaimSet.swift similarity index 67% rename from Sources/ClaimSet.swift rename to Sources/JWT/ClaimSet.swift index 7626498..4b253be 100644 --- a/Sources/ClaimSet.swift +++ b/Sources/JWT/ClaimSet.swift @@ -1,5 +1,24 @@ import Foundation +func parseTimeInterval(_ value: Any?) -> Date? { + guard let value = value else { return nil } + + if let string = value as? String, let interval = TimeInterval(string) { + return Date(timeIntervalSince1970: interval) + } + + if let interval = value as? Int { + let double = Double(interval) + return Date(timeIntervalSince1970: double) + } + + if let interval = value as? TimeInterval { + return Date(timeIntervalSince1970: interval) + } + + return nil +} + public struct ClaimSet { var claims: [String: Any] @@ -48,11 +67,7 @@ extension ClaimSet { public var expiration: Date? { get { - if let expiration = claims["exp"] as? TimeInterval { - return Date(timeIntervalSince1970: expiration) - } - - return nil + return parseTimeInterval(claims["exp"]) } set { @@ -62,11 +77,7 @@ extension ClaimSet { public var notBefore: Date? { get { - if let notBefore = claims["nbf"] as? TimeInterval { - return Date(timeIntervalSince1970: notBefore) - } - - return nil + return parseTimeInterval(claims["nbf"]) } set { @@ -76,11 +87,7 @@ extension ClaimSet { public var issuedAt: Date? { get { - if let issuedAt = claims["iat"] as? TimeInterval { - return Date(timeIntervalSince1970: issuedAt) - } - - return nil + return parseTimeInterval(claims["iat"]) } set { @@ -93,7 +100,7 @@ extension ClaimSet { // MARK: Validations extension ClaimSet { - public func validate(audience: String? = nil, issuer: String? = nil) throws { + public func validate(audience: String? = nil, issuer: String? = nil, leeway: TimeInterval = 0) throws { if let issuer = issuer { try validateIssuer(issuer) } @@ -101,10 +108,10 @@ extension ClaimSet { if let audience = audience { try validateAudience(audience) } - - try validateExpiary() - try validateNotBefore() - try validateIssuedAt() + + try validateExpiry(leeway: leeway) + try validateNotBefore(leeway: leeway) + try validateIssuedAt(leeway: leeway) } public func validateAudience(_ audience: String) throws { @@ -130,17 +137,22 @@ extension ClaimSet { throw InvalidToken.invalidIssuer } } + + @available(*, deprecated, message: "This method's name is misspelled. Please instead use validateExpiry(leeway:).") + public func validateExpiary(leeway: TimeInterval = 0) throws { + try validateExpiry(leeway: leeway) + } - public func validateExpiary() throws { - try validateDate(claims, key: "exp", comparison: .orderedAscending, failure: .expiredSignature, decodeError: "Expiration time claim (exp) must be an integer") + public func validateExpiry(leeway: TimeInterval = 0) throws { + try validateDate(claims, key: "exp", comparison: .orderedAscending, leeway: (-1 * leeway), failure: .expiredSignature, decodeError: "Expiration time claim (exp) must be an integer") } - public func validateNotBefore() throws { - try validateDate(claims, key: "nbf", comparison: .orderedDescending, failure: .immatureSignature, decodeError: "Not before claim (nbf) must be an integer") + public func validateNotBefore(leeway: TimeInterval = 0) throws { + try validateDate(claims, key: "nbf", comparison: .orderedDescending, leeway: leeway, failure: .immatureSignature, decodeError: "Not before claim (nbf) must be an integer") } - public func validateIssuedAt() throws { - try validateDate(claims, key: "iat", comparison: .orderedDescending, failure: .invalidIssuedAt, decodeError: "Issued at claim (iat) must be an integer") + public func validateIssuedAt(leeway: TimeInterval = 0) throws { + try validateDate(claims, key: "iat", comparison: .orderedDescending, leeway: leeway, failure: .invalidIssuedAt, decodeError: "Issued at claim (iat) must be an integer") } } diff --git a/Sources/Claims.swift b/Sources/JWT/Claims.swift similarity index 81% rename from Sources/Claims.swift rename to Sources/JWT/Claims.swift index ad07027..d1df6f4 100644 --- a/Sources/Claims.swift +++ b/Sources/JWT/Claims.swift @@ -1,6 +1,6 @@ import Foundation -func validateDate(_ payload: Payload, key: String, comparison: ComparisonResult, failure: InvalidToken, decodeError: String) throws { +func validateDate(_ payload: Payload, key: String, comparison: ComparisonResult, leeway: TimeInterval = 0, failure: InvalidToken, decodeError: String) throws { if payload[key] == nil { return } @@ -8,8 +8,8 @@ func validateDate(_ payload: Payload, key: String, comparison: ComparisonResult, guard let date = extractDate(payload: payload, key: key) else { throw InvalidToken.decodeError(decodeError) } - - if date.compare(Date()) == comparison { + + if date.compare(Date().addingTimeInterval(leeway)) == comparison { throw failure } } diff --git a/Sources/JWT/CompactJSONDecoder.swift b/Sources/JWT/CompactJSONDecoder.swift new file mode 100644 index 0000000..bb353d5 --- /dev/null +++ b/Sources/JWT/CompactJSONDecoder.swift @@ -0,0 +1,32 @@ +import Foundation + +class CompactJSONDecoder: JSONDecoder { + override func decode(_ type: T.Type, from data: Data) throws -> T where T : Decodable { + guard let string = String(data: data, encoding: .ascii) else { + throw InvalidToken.decodeError("data should contain only ASCII characters") + } + + return try decode(type, from: string) + } + + func decode(_ type: T.Type, from string: String) throws -> T where T : Decodable { + guard let decoded = base64decode(string) else { + throw InvalidToken.decodeError("data should be a valid base64 string") + } + + return try super.decode(type, from: decoded) + } + + func decode(from string: String) throws -> Payload { + guard let decoded = base64decode(string) else { + throw InvalidToken.decodeError("Payload is not correctly encoded as base64") + } + + let object = try JSONSerialization.jsonObject(with: decoded) + guard let payload = object as? Payload else { + throw InvalidToken.decodeError("Invalid payload") + } + + return payload + } +} diff --git a/Sources/JWT/CompactJSONEncoder.swift b/Sources/JWT/CompactJSONEncoder.swift new file mode 100644 index 0000000..2e4a10c --- /dev/null +++ b/Sources/JWT/CompactJSONEncoder.swift @@ -0,0 +1,20 @@ +import Foundation + + +class CompactJSONEncoder: JSONEncoder { + override func encode(_ value: T) throws -> Data { + return try encodeString(value).data(using: .ascii) ?? Data() + } + + func encodeString(_ value: T) throws -> String { + return base64encode(try super.encode(value)) + } + + func encodeString(_ value: [String: Any]) -> String? { + if let data = try? JSONSerialization.data(withJSONObject: value) { + return base64encode(data) + } + + return nil + } +} diff --git a/Sources/Decode.swift b/Sources/JWT/Decode.swift similarity index 57% rename from Sources/Decode.swift rename to Sources/JWT/Decode.swift index ce802d7..b6aa428 100644 --- a/Sources/Decode.swift +++ b/Sources/JWT/Decode.swift @@ -47,11 +47,11 @@ public enum InvalidToken: CustomStringConvertible, Error { /// Decode a JWT -public func decode(_ jwt: String, algorithms: [Algorithm], verify: Bool = true, audience: String? = nil, issuer: String? = nil) throws -> ClaimSet { +public func decode(_ jwt: String, algorithms: [Algorithm], verify: Bool = true, audience: String? = nil, issuer: String? = nil, leeway: TimeInterval = 0) throws -> ClaimSet { let (header, claims, signature, signatureInput) = try load(jwt) if verify { - try claims.validate(audience: audience, issuer: issuer) + try claims.validate(audience: audience, issuer: issuer, leeway: leeway) try verifySignature(algorithms, header: header, signingInput: signatureInput, signature: signature) } @@ -59,20 +59,8 @@ public func decode(_ jwt: String, algorithms: [Algorithm], verify: Bool = true, } /// Decode a JWT -public func decode(_ jwt: String, algorithm: Algorithm, verify: Bool = true, audience: String? = nil, issuer: String? = nil) throws -> ClaimSet { - return try decode(jwt, algorithms: [algorithm], verify: verify, audience: audience, issuer: issuer) -} - -/// Decode a JWT -@available(*, deprecated, message: "use decode that returns a ClaimSet instead") -public func decode(_ jwt: String, algorithms: [Algorithm], verify: Bool = true, audience: String? = nil, issuer: String? = nil) throws -> Payload { - return try decode(jwt, algorithms: algorithms, verify: verify, audience: audience, issuer: issuer).claims -} - -/// Decode a JWT -@available(*, deprecated, message: "use decode that returns a ClaimSet instead") -public func decode(_ jwt: String, algorithm: Algorithm, verify: Bool = true, audience: String? = nil, issuer: String? = nil) throws -> Payload { - return try decode(jwt, algorithms: [algorithm], verify: verify, audience: audience, issuer: issuer).claims +public func decode(_ jwt: String, algorithm: Algorithm, verify: Bool = true, audience: String? = nil, issuer: String? = nil, leeway: TimeInterval = 0) throws -> ClaimSet { + return try decode(jwt, algorithms: [algorithm], verify: verify, audience: audience, issuer: issuer, leeway: leeway) } // MARK: Parsing a JWT @@ -88,30 +76,15 @@ func load(_ jwt: String) throws -> (header: JOSEHeader, payload: ClaimSet, signa let signatureSegment = segments[2] let signatureInput = "\(headerSegment).\(payloadSegment)" - guard let headerData = base64decode(headerSegment) else { - throw InvalidToken.decodeError("Header is not correctly encoded as base64") - } - - let header = (try? JSONSerialization.jsonObject(with: headerData, options: JSONSerialization.ReadingOptions(rawValue: 0))) as? Payload - if header == nil { - throw InvalidToken.decodeError("Invalid header") - } - - let payloadData = base64decode(payloadSegment) - if payloadData == nil { - throw InvalidToken.decodeError("Payload is not correctly encoded as base64") - } - - let payload = (try? JSONSerialization.jsonObject(with: payloadData!, options: JSONSerialization.ReadingOptions(rawValue: 0))) as? Payload - if payload == nil { - throw InvalidToken.decodeError("Invalid payload") - } + let decoder = CompactJSONDecoder() + let header = try decoder.decode(JOSEHeader.self, from: headerSegment) + let payload = try decoder.decode(from: payloadSegment) guard let signature = base64decode(signatureSegment) else { throw InvalidToken.decodeError("Signature is not correctly encoded as base64") } - return (header: JOSEHeader(parameters: header!), payload: ClaimSet(claims: payload!), signature: signature, signatureInput: signatureInput) + return (header: header, payload: ClaimSet(claims: payload), signature: signature, signatureInput: signatureInput) } // MARK: Signature Verification @@ -123,7 +96,7 @@ func verifySignature(_ algorithms: [Algorithm], header: JOSEHeader, signingInput let verifiedAlgorithms = algorithms .filter { algorithm in algorithm.description == alg } - .filter { algorithm in algorithm.verify(signingInput, signature: signature) } + .filter { algorithm in algorithm.algorithm.verify(signingInput.data(using: .utf8)!, signature: signature) } if verifiedAlgorithms.isEmpty { throw InvalidToken.invalidAlgorithm diff --git a/Sources/Encode.swift b/Sources/JWT/Encode.swift similarity index 62% rename from Sources/Encode.swift rename to Sources/JWT/Encode.swift index fa716d1..22d1960 100644 --- a/Sources/Encode.swift +++ b/Sources/JWT/Encode.swift @@ -1,18 +1,13 @@ import Foundation + /*** Encode a set of claims - parameter claims: The set of claims - parameter algorithm: The algorithm to sign the payload with - returns: The JSON web token as a String */ public func encode(claims: ClaimSet, algorithm: Algorithm, headers: [String: String]? = nil) -> String { - func encodeJSON(_ payload: [String: Any]) -> String? { - if let data = try? JSONSerialization.data(withJSONObject: payload) { - return base64encode(data) - } - - return nil - } + let encoder = CompactJSONEncoder() var headers = headers ?? [:] if !headers.keys.contains("typ") { @@ -20,10 +15,10 @@ public func encode(claims: ClaimSet, algorithm: Algorithm, headers: [String: Str } headers["alg"] = algorithm.description - let header = encodeJSON(headers)! - let payload = encodeJSON(claims.claims)! + let header = try! encoder.encodeString(headers) + let payload = encoder.encodeString(claims.claims)! let signingInput = "\(header).\(payload)" - let signature = algorithm.sign(signingInput) + let signature = base64encode(algorithm.algorithm.sign(signingInput.data(using: .utf8)!)) return "\(signingInput).\(signature)" } @@ -43,14 +38,3 @@ public func encode(_ algorithm: Algorithm, closure: ((ClaimSetBuilder) -> Void)) closure(builder) return encode(claims: builder.claims, algorithm: algorithm) } - - -/*** Encode a payload - - parameter payload: The payload to sign - - parameter algorithm: The algorithm to sign the payload with - - returns: The JSON web token as a String - */ -@available(*, deprecated, message: "use encode(claims: algorithm:) instead") -public func encode(_ payload: Payload, algorithm: Algorithm) -> String { - return encode(claims: ClaimSet(claims: payload), algorithm: algorithm) -} diff --git a/Sources/JWT/JOSEHeader.swift b/Sources/JWT/JOSEHeader.swift new file mode 100644 index 0000000..3e673e7 --- /dev/null +++ b/Sources/JWT/JOSEHeader.swift @@ -0,0 +1,68 @@ +// +// JOSEHeader.swift +// JWT +// +// Created by Kyle Fuller on 02/12/2016. +// Copyright © 2016 Cocode. All rights reserved. +// + +import Foundation + + +struct JOSEHeader: Codable { + /// The "alg" (algorithm) identifies the cryptographic algorithm used to secure the JWS + var algorithm: String? + + /// jwu + // TODO + + /// jwk + // TODO + + /// The "kid" (key ID) is a hint indicating which key was used to secure the JWS + var keyID: String? + + /// x5u + // TODO + + /// x5c + // TODO + + /// x5t + // TODO + + /// x5t#S256 + // TODO + + /// The "typ" (type) is used by JWS applications to declare the media type [IANA.MediaTypes] of this complete JWS + var type: String? + + /// The "cty" (content type) is used by JWS application to declare the media type [IANA.MediaTypes] of the secured content (the payload). + var contentType: String? + + /// The "crit" (critical) indicates that extensions to JWS, JWE and/or [JWA] are being used that MUST be understood and processed + // TODO + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + algorithm = try container.decodeIfPresent(String.self, forKey: .algorithm) + keyID = try container.decodeIfPresent(String.self, forKey: .keyID) + type = try container.decodeIfPresent(String.self, forKey: .type) + contentType = try container.decodeIfPresent(String.self, forKey: .contentType) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(algorithm, forKey: .algorithm) + try container.encodeIfPresent(keyID, forKey: .keyID) + try container.encodeIfPresent(type, forKey: .type) + try container.encodeIfPresent(contentType, forKey: .contentType) + } + + enum CodingKeys: String, CodingKey { + case algorithm = "alg" + case keyID = "kid" + case type = "typ" + case contentType = "cty" + } +} diff --git a/Sources/JWT/JWT.swift b/Sources/JWT/JWT.swift new file mode 100644 index 0000000..5e5569e --- /dev/null +++ b/Sources/JWT/JWT.swift @@ -0,0 +1,3 @@ +import Foundation + +public typealias Payload = [String: Any] diff --git a/Tests/JWATests/HMACTests.swift b/Tests/JWATests/HMACTests.swift new file mode 100644 index 0000000..2991630 --- /dev/null +++ b/Tests/JWATests/HMACTests.swift @@ -0,0 +1,63 @@ +import Foundation +import XCTest +import JWA + + +class HMACAlgorithmTests: XCTestCase { + let key = "secret".data(using: .utf8)! + let message = "message".data(using: .utf8)! + let sha256Signature = Data(base64Encoded: "i19IcCmVwVmMVz2x4hhmqbgl1KeU0WnXBgoDYFeWNgs=")! + let sha384Signature = Data(base64Encoded: "rQ706A2kJ7KjPURXyXK/dZ9Qdm+7ZlaQ1Qt8s43VIX21Wck+p8vuSOKuGltKr9NL")! + let sha512Signature = Data(base64Encoded: "G7pYfHMO7box9Tq7C2ylieCd5OiU7kVeYUCAc5l1mtqvoGnux8AWR7sXPcsX9V0ir0mhgHG3SMXC7df3qCnGMg==")! + + // MARK: Name + + func testSHA256Name() { + let algorithm = HMACAlgorithm(key: key, hash: .sha256) + XCTAssertEqual(algorithm.name, "HS256") + } + + func testSHA384Name() { + let algorithm = HMACAlgorithm(key: key, hash: .sha384) + XCTAssertEqual(algorithm.name, "HS384") + } + + func testSHA512Name() { + let algorithm = HMACAlgorithm(key: key, hash: .sha512) + XCTAssertEqual(algorithm.name, "HS512") + } + + // MARK: Signing + + func testSHA256Sign() { + let algorithm = HMACAlgorithm(key: key, hash: .sha256) + XCTAssertEqual(algorithm.sign(message), sha256Signature) + } + + func testSHA384Sign() { + let algorithm = HMACAlgorithm(key: key, hash: .sha384) + XCTAssertEqual(algorithm.sign(message), sha384Signature) + } + + func testSHA512Sign() { + let algorithm = HMACAlgorithm(key: key, hash: .sha512) + XCTAssertEqual(algorithm.sign(message), sha512Signature) + } + + // MARK: Verify + + func testSHA256Verify() { + let algorithm = HMACAlgorithm(key: key, hash: .sha256) + XCTAssertTrue(algorithm.verify(message, signature: sha256Signature)) + } + + func testSHA384Verify() { + let algorithm = HMACAlgorithm(key: key, hash: .sha384) + XCTAssertTrue(algorithm.verify(message, signature: sha384Signature)) + } + + func testSHA512Verify() { + let algorithm = HMACAlgorithm(key: key, hash: .sha512) + XCTAssertTrue(algorithm.verify(message, signature: sha512Signature)) + } +} diff --git a/Tests/JWATests/NoneTests.swift b/Tests/JWATests/NoneTests.swift new file mode 100644 index 0000000..c3987c5 --- /dev/null +++ b/Tests/JWATests/NoneTests.swift @@ -0,0 +1,23 @@ +import XCTest +import JWA + + +class NoneAlgorithmTests: XCTestCase { + let message = "message".data(using: .utf8)! + let signature = Data() + + func testName() { + let algorithm = NoneAlgorithm() + XCTAssertEqual(algorithm.name, "none") + } + + func testSign() { + let algorithm = NoneAlgorithm() + XCTAssertEqual(algorithm.sign(message), signature) + } + + func testVerify() { + let algorithm = NoneAlgorithm() + XCTAssertTrue(algorithm.verify(message, signature: signature)) + } +} diff --git a/Tests/JWTTests/ClaimSetTests.swift b/Tests/JWTTests/ClaimSetTests.swift new file mode 100644 index 0000000..aa8e8dd --- /dev/null +++ b/Tests/JWTTests/ClaimSetTests.swift @@ -0,0 +1,79 @@ +import XCTest +import JWT + +class ValidationTests: XCTestCase { + func testClaimJustExpiredWithoutLeeway() { + var claims = ClaimSet() + claims.expiration = Date().addingTimeInterval(-1) + + do { + try claims.validateExpiry() + XCTFail("InvalidToken.expiredSignature error should have been thrown.") + } catch InvalidToken.expiredSignature { + // Correct error thrown + } catch { + XCTFail("Unexpected error while validating exp claim.") + } + } + + func testClaimJustNotExpiredWithoutLeeway() { + var claims = ClaimSet() + claims.expiration = Date().addingTimeInterval(-1) + + do { + try claims.validateExpiry(leeway: 2) + } catch { + XCTFail("Unexpected error while validating exp claim that should be valid with leeway.") + } + } + + func testNotBeforeIsImmatureSignatureWithoutLeeway() { + var claims = ClaimSet() + claims.notBefore = Date().addingTimeInterval(1) + + do { + try claims.validateNotBefore() + XCTFail("InvalidToken.immatureSignature error should have been thrown.") + } catch InvalidToken.immatureSignature { + // Correct error thrown + } catch { + XCTFail("Unexpected error while validating nbf claim.") + } + } + + func testNotBeforeIsValidWithLeeway() { + var claims = ClaimSet() + claims.notBefore = Date().addingTimeInterval(1) + + do { + try claims.validateNotBefore(leeway: 2) + } catch { + XCTFail("Unexpected error while validating nbf claim that should be valid with leeway.") + } + } + + func testIssuedAtIsInFutureWithoutLeeway() { + var claims = ClaimSet() + claims.issuedAt = Date().addingTimeInterval(1) + + do { + try claims.validateIssuedAt() + XCTFail("InvalidToken.invalidIssuedAt error should have been thrown.") + } catch InvalidToken.invalidIssuedAt { + // Correct error thrown + } catch { + XCTFail("Unexpected error while validating iat claim.") + } + } + + func testIssuedAtIsValidWithLeeway() { + var claims = ClaimSet() + claims.issuedAt = Date().addingTimeInterval(1) + + do { + try claims.validateIssuedAt(leeway: 2) + } catch { + XCTFail("Unexpected error while validating iat claim that should be valid with leeway.") + } + } +} diff --git a/Tests/JWTTests/CompactJSONDecoderTests.swift b/Tests/JWTTests/CompactJSONDecoderTests.swift new file mode 100644 index 0000000..ed1bf26 --- /dev/null +++ b/Tests/JWTTests/CompactJSONDecoderTests.swift @@ -0,0 +1,16 @@ +import XCTest +@testable import JWT + +class CompactJSONDecodable: Decodable { + let key: String +} + +class CompactJSONDecoderTests: XCTestCase { + let decoder = CompactJSONDecoder() + + func testDecoder() throws { + let expected = "eyJrZXkiOiJ2YWx1ZSJ9".data(using: .ascii)! + let value = try decoder.decode(CompactJSONDecodable.self, from: expected) + XCTAssertEqual(value.key, "value") + } +} diff --git a/Tests/JWTTests/CompactJSONEncoderTests.swift b/Tests/JWTTests/CompactJSONEncoderTests.swift new file mode 100644 index 0000000..a0f59cd --- /dev/null +++ b/Tests/JWTTests/CompactJSONEncoderTests.swift @@ -0,0 +1,23 @@ +import XCTest +@testable import JWT + +class CompactJSONEncodable: Encodable { + let key: String + + init(key: String) { + self.key = key + } +} + +class CompactJSONEncoderTests: XCTestCase { + let encoder = CompactJSONEncoder() + + func testEncode() throws { + let value = CompactJSONEncodable(key: "value") + + let encoded = try encoder.encode(value) + + XCTAssertEqual(encoded, "eyJrZXkiOiJ2YWx1ZSJ9".data(using: .ascii)!) + } +} + diff --git a/Tests/JWTTests/IntegrationTests.swift b/Tests/JWTTests/IntegrationTests.swift new file mode 100644 index 0000000..f15b9d9 --- /dev/null +++ b/Tests/JWTTests/IntegrationTests.swift @@ -0,0 +1,40 @@ +import XCTest +import JWT + +class IntegrationTests: XCTestCase { + func testVerificationFailureWithoutLeeway() { + let token = JWT.encode(.none) { builder in + builder.issuer = "fuller.li" + builder.audience = "cocoapods" + builder.expiration = Date().addingTimeInterval(-1) // Token expired one second ago + builder.notBefore = Date().addingTimeInterval(1) // Token starts being valid in one second + builder.issuedAt = Date().addingTimeInterval(1) // Token is issued one second in the future + } + + do { + let _ = try JWT.decode(token, algorithm: .none, leeway: 0) + XCTFail("InvalidToken error should have been thrown.") + } catch is InvalidToken { + // Correct error thrown + } catch { + XCTFail("Unexpected error type while verifying token.") + } + } + + func testVerificationSuccessWithLeeway() { + let token = JWT.encode(.none) { builder in + builder.issuer = "fuller.li" + builder.audience = "cocoapods" + builder.expiration = Date().addingTimeInterval(-1) // Token expired one second ago + builder.notBefore = Date().addingTimeInterval(1) // Token starts being valid in one second + builder.issuedAt = Date().addingTimeInterval(1) // Token is issued one second in the future + } + + do { + let _ = try JWT.decode(token, algorithm: .none, leeway: 2) + // Due to leeway no error gets thrown. + } catch { + XCTFail("Unexpected error type while verifying token.") + } + } +} diff --git a/Tests/JWTTests/JWTDecodeTests.swift b/Tests/JWTTests/JWTDecodeTests.swift new file mode 100644 index 0000000..cde1a10 --- /dev/null +++ b/Tests/JWTTests/JWTDecodeTests.swift @@ -0,0 +1,220 @@ +import Foundation +import XCTest +@testable import JWT + +class DecodeTests: XCTestCase { + func testDecodingValidJWT() throws { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiS3lsZSJ9.zxm7xcp1eZtZhp4t-nlw09ATQnnFKIiSN83uG8u6cAg" + + let claims = try JWT.decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!)) + XCTAssertEqual(claims["name"] as? String, "Kyle") + } + + func testFailsToDecodeInvalidStringWithoutThreeSegments() { + XCTAssertThrowsError(try decode("a.b", algorithm: .none), "Not enough segments") + } + + // MARK: Disable verify + + func testDisablingVerify() throws { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w" + _ = try decode(jwt, algorithm: .none, verify: false, issuer: "fuller.li") + } + + // MARK: Issuer claim + + func testSuccessfulIssuerValidation() throws { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmdWxsZXIubGkifQ.d7B7PAQcz1E6oNhrlxmHxHXHgg39_k7X7wWeahl8kSQ" + + let claims = try JWT.decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!)) + XCTAssertEqual(claims.issuer, "fuller.li") + } + + func testIncorrectIssuerValidation() { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmdWxsZXIubGkifQ.wOhJ9_6lx-3JGJPmJmtFCDI3kt7uMAMmhHIslti7ryI" + XCTAssertThrowsError(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!), issuer: "querykit.org")) + } + + func testMissingIssuerValidation() { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w" + XCTAssertThrowsError(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!), issuer: "fuller.li")) + } + + // MARK: Expiration claim + + func testExpiredClaim() { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0MjgxODg0OTF9.cy6b2szsNkKnHFnz2GjTatGjoHBTs8vBKnPGZgpp91I" + XCTAssertThrowsError(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) + } + + func testInvalidExpiryClaim() { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOlsiMTQyODE4ODQ5MSJdfQ.OwF-wd3THjxrEGUhh6IdnNhxQZ7ydwJ3Z6J_dfl9MBs" + XCTAssertThrowsError(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) + } + + func testUnexpiredClaim() throws { + // If this just started failing, hello 2024! + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjgxODg0OTF9.EW7k-8Mvnv0GpvOKJalFRLoCB3a3xGG3i7hAZZXNAz0" + + let claims = try JWT.decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!)) + XCTAssertEqual(claims.expiration?.timeIntervalSince1970, 1728188491) + } + + func testUnexpiredClaimString() throws { + // If this just started failing, hello 2024! + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxNzI4MTg4NDkxIn0.y4w7lNLrfRRPzuNUfM-ZvPkoOtrTU_d8ZVYasLdZGpk" + + let claims = try JWT.decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!)) + XCTAssertEqual(claims.expiration?.timeIntervalSince1970, 1728188491) + } + + // MARK: Not before claim + + func testNotBeforeClaim() throws { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0MjgxODk3MjB9.jFT0nXAJvEwyG6R7CMJlzNJb7FtZGv30QRZpYam5cvs" + + let claims = try JWT.decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!)) + XCTAssertEqual(claims.notBefore?.timeIntervalSince1970, 1428189720) + } + + func testNotBeforeClaimString() throws { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOiIxNDI4MTg5NzIwIn0.qZsj36irdmIAeXv6YazWDSFbpuxHtEh4Deof5YTpnVI" + + let claims = try JWT.decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!)) + XCTAssertEqual(claims.notBefore?.timeIntervalSince1970, 1428189720) + } + + func testInvalidNotBeforeClaim() { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOlsxNDI4MTg5NzIwXX0.PUL1FQubzzJa4MNXe2D3d5t5cMaqFr3kYlzRUzly-C8" + assertDecodeError(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!)), error: "Not before claim (nbf) must be an integer") + } + + func testUnmetNotBeforeClaim() { + // If this just started failing, hello 2024! + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3MjgxODg0OTF9.Tzhu1tu-7BXcF5YEIFFE1Vmg4tEybUnaz58FR4PcblQ" + XCTAssertThrowsError(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) + } + + // MARK: Issued at claim + + func testIssuedAtClaimInThePast() throws { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MjgxODk3MjB9.I_5qjRcCUZVQdABLwG82CSuu2relSdIyJOyvXWUAJh4" + + let claims = try JWT.decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!)) + XCTAssertEqual(claims.issuedAt?.timeIntervalSince1970, 1428189720) + } + + func testIssuedAtClaimInThePastString() throws { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOiIxNDI4MTg5NzIwIn0.M8veWtsY52oBwi7LRKzvNnzhjK0QBS8Su1r0atlns2k" + + let claims = try JWT.decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!)) + XCTAssertEqual(claims.issuedAt?.timeIntervalSince1970, 1428189720) + } + + func testIssuedAtClaimInTheFuture() { + // If this just started failing, hello 2024! + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MjgxODg0OTF9.owHiJyJmTcW1lBW5y_Rz3iBfSbcNiXlbZ2fY9qR7-aU" + XCTAssertThrowsError(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) + } + + func testInvalidIssuedAtClaim() { + // If this just started failing, hello 2024! + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOlsxNzI4MTg4NDkxXX0.ND7QMWtLkXDXH38OaXM3SQgLo3Z5TNgF_pcfWHV_alQ" + assertDecodeError(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!)), error: "Issued at claim (iat) must be an integer") + } + + // MARK: Audience claims + + func testAudiencesClaim() { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsibWF4aW5lIiwia2F0aWUiXX0.-PKvdNLCClrWG7CvesHP6PB0-vxu-_IZcsYhJxBy5JM" + assertSuccess(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!), audience: "maxine")) { payload in + XCTAssertEqual(payload.count, 1) + XCTAssertEqual(payload["aud"] as! [String], ["maxine", "katie"]) + } + } + + func testAudienceClaim() { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJreWxlIn0.dpgH4JOwueReaBoanLSxsGTc7AjKUvo7_M1sAfy_xVE" + assertSuccess(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!), audience: "kyle")) { payload in + XCTAssertEqual(payload as! [String: String], ["aud": "kyle"]) + } + } + + func testMismatchAudienceClaim() { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJreWxlIn0.VEB_n06pTSLlTXPFkc46ARADJ9HXNUBUPo3VhL9RDe4" // kyle + XCTAssertThrowsError(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!), audience: "maxine")) + } + + func testMissingAudienceClaim() { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w" + XCTAssertThrowsError(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!), audience: "kyle")) + } + + // MARK: Signature verification + + func testNoneAlgorithm() { + let jwt = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ0ZXN0IjoiaW5nIn0." + assertSuccess(try decode(jwt, algorithm: .none)) { payload in + XCTAssertEqual(payload as! [String: String], ["test": "ing"]) + } + } + + func testNoneFailsWithSecretAlgorithm() { + let jwt = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ0ZXN0IjoiaW5nIn0." + XCTAssertThrowsError(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) + } + + func testMatchesAnyAlgorithm() { + let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w." + assertFailure(try decode(jwt, algorithms: [.hs256("anothersecret".data(using: .utf8)!), .hs256("secret".data(using: .utf8)!)])) + } + + func testHS384Algorithm() { + let jwt = "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.lddiriKLoo42qXduMhCTKZ5Lo3njXxOC92uXyvbLyYKzbq4CVVQOb3MpDwnI19u4" + assertSuccess(try decode(jwt, algorithm: .hs384("secret".data(using: .utf8)!))) { payload in + XCTAssertEqual(payload as! [String: String], ["some": "payload"]) + } + } + + func testHS512Algorithm() { + let jwt = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.WTzLzFO079PduJiFIyzrOah54YaM8qoxH9fLMQoQhKtw3_fMGjImIOokijDkXVbyfBqhMo2GCNu4w9v7UXvnpA" + assertSuccess(try decode(jwt, algorithm: .hs512("secret".data(using: .utf8)!))) { claims in + XCTAssertEqual(claims as! [String: String], ["some": "payload"]) + } + } +} + +// MARK: Helpers + +func assertSuccess(_ decoder: @autoclosure () throws -> ClaimSet, closure: (([String: Any]) -> Void)? = nil) { + do { + let claims = try decoder() + closure?(claims.claims as [String: Any]) + } catch { + XCTFail("Failed to decode while expecting success. \(error)") + } +} + +func assertFailure(_ decoder: @autoclosure () throws -> ClaimSet, closure: ((InvalidToken) -> Void)? = nil) { + do { + _ = try decoder() + XCTFail("Decoding succeeded, expected a failure.") + } catch let error as InvalidToken { + closure?(error) + } catch { + XCTFail("Unexpected error") + } +} + +func assertDecodeError(_ decoder: @autoclosure () throws -> ClaimSet, error: String) { + assertFailure(try decoder()) { failure in + switch failure { + case .decodeError(let decodeError): + if decodeError != error { + XCTFail("Incorrect decode error \(decodeError) != \(error)") + } + default: + XCTFail("Failure for the wrong reason \(failure)") + } + } +} diff --git a/Tests/JWTTests/JWTEncodeTests.swift b/Tests/JWTTests/JWTEncodeTests.swift new file mode 100644 index 0000000..9ff7f14 --- /dev/null +++ b/Tests/JWTTests/JWTEncodeTests.swift @@ -0,0 +1,58 @@ +import XCTest +import JWT + + +class JWTEncodeTests: XCTestCase { + func testEncodingJWT() { + let payload = ["name": "Kyle"] as Payload + let jwt = JWT.encode(claims: payload, algorithm: .hs256("secret".data(using: .utf8)!)) + + let expected = [ + // { "alg": "HS256", "typ": "JWT" } + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiS3lsZSJ9.zxm7xcp1eZtZhp4t-nlw09ATQnnFKIiSN83uG8u6cAg", + + // { "typ": "JWT", "alg": "HS256" } + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiS3lsZSJ9.4tCpoxfyfjbUyLjm9_zu-r52Vxn6bFq9kp6Rt9xMs4A" + ] + + XCTAssertTrue(expected.contains(jwt)) + } + + func testEncodingWithBuilder() { + let algorithm = Algorithm.hs256("secret".data(using: .utf8)!) + let jwt = JWT.encode(algorithm) { builder in + builder.issuer = "fuller.li" + } + + let expected = [ + // { "alg": "HS256", "typ": "JWT" } + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmdWxsZXIubGkifQ.d7B7PAQcz1E6oNhrlxmHxHXHgg39_k7X7wWeahl8kSQ", + // { "typ": "JWT", "alg": "HS256" } + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJmdWxsZXIubGkifQ.x5Fdll-kZBImOPtpT1fZH_8hDW01Ax3pbZx_EiljoLk" + ] + + XCTAssertTrue(expected.contains(jwt)) + } + + func testEncodingClaimsWithHeaders() { + let algorithm = Algorithm.hs256("secret".data(using: .utf8)!) + let jwt = JWT.encode(claims: ClaimSet(), algorithm: algorithm, headers: ["kid": "x"]) + + let expected = [ + // { "alg": "HS256", "typ": "JWT", "kid": "x" } + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IngifQ.e30.ddEotxYYMMdat5HPgYFQnkHRdPXsxPG71ooyhIUoqGA", + // { "alg": "HS256", "kid": "x", "typ": "JWT" } + "eyJhbGciOiJIUzI1NiIsImtpZCI6IngiLCJ0eXAiOiJKV1QifQ.e30.xiT6fWe5dWGeuq8zFb0je_14Maa_9mHbVPSyJhUIJ54", + // { "typ": "JWT", "alg": "HS256", "kid": "x" } + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IngifQ.e30.5t6a61tpSXFo5QBHYCnKAz2mTHrW9kaQ9n_b7e-jWw0", + // { "typ": "JWT", "kid": "x", "alg": "HS256" } + "eyJ0eXAiOiJKV1QiLCJraWQiOiJ4IiwiYWxnIjoiSFMyNTYifQ.e30.DG5nmV2CVH6mV_iEm0xXZvL0DUJ22ek2xy6fNi_pGLc", + // { "kid": "x", "typ": "JWT", "alg": "HS256" } + "eyJraWQiOiJ4IiwidHlwIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.h5ZvlqECBIvu9uocR5_5uF3wnhga8vTruvXpzaHpRdA", + // { "kid": "x", "alg": "HS256", "typ": "JWT" } + "eyJraWQiOiJ4IiwiYWxnIjoiSFMyNTYiLCJ0eXAiOiJKV1QifQ.e30.5KqN7N5a7Cfbe2eKN41FJIfgMjcdSZ7Nt16xqlyOeMo" + ] + + XCTAssertTrue(expected.contains(jwt)) + } +} diff --git a/Tests/JWTTests/JWTTests.swift b/Tests/JWTTests/JWTTests.swift deleted file mode 100644 index a0e1b8f..0000000 --- a/Tests/JWTTests/JWTTests.swift +++ /dev/null @@ -1,315 +0,0 @@ -import Foundation -import XCTest -import JWT - -class EncodeTests: XCTestCase { - func testEncodingJWT() { - let payload = ["name": "Kyle"] as Payload - let jwt = JWT.encode(payload, algorithm: .hs256("secret".data(using: .utf8)!)) - - let expected = [ - // { "alg": "HS256", "typ": "JWT" } - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiS3lsZSJ9.zxm7xcp1eZtZhp4t-nlw09ATQnnFKIiSN83uG8u6cAg", - - // { "typ": "JWT", "alg": "HS256" } - "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiS3lsZSJ9.4tCpoxfyfjbUyLjm9_zu-r52Vxn6bFq9kp6Rt9xMs4A", - ] - - XCTAssertTrue(expected.contains(jwt)) - } - - func testEncodingWithBuilder() { - let algorithm = Algorithm.hs256("secret".data(using: .utf8)!) - let jwt = JWT.encode(algorithm) { builder in - builder.issuer = "fuller.li" - } - - assertSuccess(try JWT.decode(jwt, algorithm: algorithm)) { payload in - XCTAssertEqual(payload as! [String: String], ["iss": "fuller.li"]) - } - } - - func testEncodingClaimsWithHeaders() { - let algorithm = Algorithm.hs256("secret".data(using: .utf8)!) - let jwt = JWT.encode(claims: ClaimSet(), algorithm: algorithm, headers: ["kid": "x"]) - - XCTAssertEqual(jwt, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IngifQ.e30.ddEotxYYMMdat5HPgYFQnkHRdPXsxPG71ooyhIUoqGA") - } -} - -class PayloadTests: XCTestCase { - func testIssuer() { - _ = JWT.encode(.none) { builder in - builder.issuer = "fuller.li" - XCTAssertEqual(builder.issuer, "fuller.li") - XCTAssertEqual(builder["iss"] as? String, "fuller.li") - } - } - - func testAudience() { - _ = JWT.encode(.none) { builder in - builder.audience = "cocoapods" - XCTAssertEqual(builder.audience, "cocoapods") - XCTAssertEqual(builder["aud"] as? String, "cocoapods") - } - } - - func testExpiration() { - _ = JWT.encode(.none) { builder in - let date = Date(timeIntervalSince1970: Date().timeIntervalSince1970) - builder.expiration = date - XCTAssertEqual(builder.expiration, date) - XCTAssertEqual(builder["exp"] as? TimeInterval, date.timeIntervalSince1970) - } - } - - func testNotBefore() { - _ = JWT.encode(.none) { builder in - let date = Date(timeIntervalSince1970: Date().timeIntervalSince1970) - builder.notBefore = date - XCTAssertEqual(builder.notBefore, date) - XCTAssertEqual(builder["nbf"] as? TimeInterval, date.timeIntervalSince1970) - } - } - - func testIssuedAt() { - _ = JWT.encode(.none) { builder in - let date = Date(timeIntervalSince1970: Date().timeIntervalSince1970) - builder.issuedAt = date - XCTAssertEqual(builder.issuedAt, date) - XCTAssertEqual(builder["iat"] as? TimeInterval, date.timeIntervalSince1970) - } - } - - func testCustomAttributes() { - _ = JWT.encode(.none) { builder in - builder["user"] = "kyle" - XCTAssertEqual(builder["user"] as? String, "kyle") - } - } -} - -class DecodeTests: XCTestCase { - func testDecodingValidJWTAsClaimSet() throws { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiS3lsZSJ9.zxm7xcp1eZtZhp4t-nlw09ATQnnFKIiSN83uG8u6cAg" - - let claims: ClaimSet = try JWT.decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!)) - XCTAssertEqual(claims["name"] as? String, "Kyle") - } - - func testDecodingValidJWT() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiS3lsZSJ9.zxm7xcp1eZtZhp4t-nlw09ATQnnFKIiSN83uG8u6cAg" - - assertSuccess(try JWT.decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) { payload in - XCTAssertEqual(payload as! [String: String], ["name": "Kyle"]) - } - } - - func testFailsToDecodeInvalidStringWithoutThreeSegments() { - assertDecodeError(try decode("a.b", algorithm: .none), error: "Not enough segments") - } - - // MARK: Disable verify - - func testDisablingVerify() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w" - assertSuccess(try decode(jwt, algorithm: .none, verify: false, issuer: "fuller.li")) - } - - // MARK: Issuer claim - - func testSuccessfulIssuerValidation() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmdWxsZXIubGkifQ.d7B7PAQcz1E6oNhrlxmHxHXHgg39_k7X7wWeahl8kSQ" - assertSuccess(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!), issuer: "fuller.li")) { payload in - XCTAssertEqual(payload as! [String: String], ["iss": "fuller.li"]) - } - } - - func testIncorrectIssuerValidation() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmdWxsZXIubGkifQ.wOhJ9_6lx-3JGJPmJmtFCDI3kt7uMAMmhHIslti7ryI" - assertFailure(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!), issuer: "querykit.org")) - } - - func testMissingIssuerValidation() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w" - assertFailure(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!), issuer: "fuller.li")) - } - - // MARK: Expiration claim - - func testExpiredClaim() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0MjgxODg0OTF9.cy6b2szsNkKnHFnz2GjTatGjoHBTs8vBKnPGZgpp91I" - assertFailure(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) - } - - func testInvalidExpiaryClaim() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOlsiMTQyODE4ODQ5MSJdfQ.OwF-wd3THjxrEGUhh6IdnNhxQZ7ydwJ3Z6J_dfl9MBs" - assertFailure(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) - } - - func testUnexpiredClaim() { - // If this just started failing, hello 2024! - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjgxODg0OTF9.EW7k-8Mvnv0GpvOKJalFRLoCB3a3xGG3i7hAZZXNAz0" - assertSuccess(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) { payload in - XCTAssertEqual(payload as! [String: Int], ["exp": 1728188491]) - } - } - - func testUnexpiredClaimString() { - // If this just started failing, hello 2024! - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxNzI4MTg4NDkxIn0.y4w7lNLrfRRPzuNUfM-ZvPkoOtrTU_d8ZVYasLdZGpk" - assertSuccess(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) { payload in - XCTAssertEqual(payload as! [String: String], ["exp": "1728188491"]) - } - } - - // MARK: Not before claim - - func testNotBeforeClaim() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0MjgxODk3MjB9.jFT0nXAJvEwyG6R7CMJlzNJb7FtZGv30QRZpYam5cvs" - assertSuccess(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) { payload in - XCTAssertEqual(payload as! [String: Int], ["nbf": 1428189720]) - } - } - - func testNotBeforeClaimString() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOiIxNDI4MTg5NzIwIn0.qZsj36irdmIAeXv6YazWDSFbpuxHtEh4Deof5YTpnVI" - assertSuccess(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) { payload in - XCTAssertEqual(payload as! [String: String], ["nbf": "1428189720"]) - } - } - - func testInvalidNotBeforeClaim() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOlsxNDI4MTg5NzIwXX0.PUL1FQubzzJa4MNXe2D3d5t5cMaqFr3kYlzRUzly-C8" - assertDecodeError(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!)), error: "Not before claim (nbf) must be an integer") - } - - func testUnmetNotBeforeClaim() { - // If this just started failing, hello 2024! - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3MjgxODg0OTF9.Tzhu1tu-7BXcF5YEIFFE1Vmg4tEybUnaz58FR4PcblQ" - assertFailure(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) - } - - // MARK: Issued at claim - - func testIssuedAtClaimInThePast() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MjgxODk3MjB9.I_5qjRcCUZVQdABLwG82CSuu2relSdIyJOyvXWUAJh4" - assertSuccess(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) { payload in - XCTAssertEqual(payload as! [String: Int], ["iat": 1428189720]) - } - } - - func testIssuedAtClaimInThePastString() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOiIxNDI4MTg5NzIwIn0.M8veWtsY52oBwi7LRKzvNnzhjK0QBS8Su1r0atlns2k" - assertSuccess(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) { payload in - XCTAssertEqual(payload as! [String: String], ["iat": "1428189720"]) - } - } - - func testIssuedAtClaimInTheFuture() { - // If this just started failing, hello 2024! - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MjgxODg0OTF9.owHiJyJmTcW1lBW5y_Rz3iBfSbcNiXlbZ2fY9qR7-aU" - assertFailure(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) - } - - func testInvalidIssuedAtClaim() { - // If this just started failing, hello 2024! - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOlsxNzI4MTg4NDkxXX0.ND7QMWtLkXDXH38OaXM3SQgLo3Z5TNgF_pcfWHV_alQ" - assertDecodeError(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!)), error: "Issued at claim (iat) must be an integer") - } - - // MARK: Audience claims - - func testAudiencesClaim() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsibWF4aW5lIiwia2F0aWUiXX0.-PKvdNLCClrWG7CvesHP6PB0-vxu-_IZcsYhJxBy5JM" - assertSuccess(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!), audience: "maxine")) { payload in - XCTAssertEqual(payload.count, 1) - XCTAssertEqual(payload["aud"] as! [String], ["maxine", "katie"]) - } - } - - func testAudienceClaim() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJreWxlIn0.dpgH4JOwueReaBoanLSxsGTc7AjKUvo7_M1sAfy_xVE" - assertSuccess(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!), audience: "kyle")) { payload in - XCTAssertEqual(payload as! [String: String], ["aud": "kyle"]) - } - } - - func testMismatchAudienceClaim() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJreWxlIn0.VEB_n06pTSLlTXPFkc46ARADJ9HXNUBUPo3VhL9RDe4" // kyle - assertFailure(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!), audience: "maxine")) - } - - func testMissingAudienceClaim() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w" - assertFailure(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!), audience: "kyle")) - } - - // MARK: Signature verification - - func testNoneAlgorithm() { - let jwt = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ0ZXN0IjoiaW5nIn0." - assertSuccess(try decode(jwt, algorithm: .none)) { payload in - XCTAssertEqual(payload as! [String: String], ["test": "ing"]) - } - } - - func testNoneFailsWithSecretAlgorithm() { - let jwt = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ0ZXN0IjoiaW5nIn0." - assertFailure(try decode(jwt, algorithm: .hs256("secret".data(using: .utf8)!))) - } - - func testMatchesAnyAlgorithm() { - let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w." - assertFailure(try decode(jwt, algorithms: [.hs256("anothersecret".data(using: .utf8)!), .hs256("secret".data(using: .utf8)!)])) - } - - func testHS384Algorithm() { - let jwt = "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.lddiriKLoo42qXduMhCTKZ5Lo3njXxOC92uXyvbLyYKzbq4CVVQOb3MpDwnI19u4" - assertSuccess(try decode(jwt, algorithm: .hs384("secret".data(using: .utf8)!))) { payload in - XCTAssertEqual(payload as! [String: String], ["some": "payload"]) - } - } - - func testHS512Algorithm() { - let jwt = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.WTzLzFO079PduJiFIyzrOah54YaM8qoxH9fLMQoQhKtw3_fMGjImIOokijDkXVbyfBqhMo2GCNu4w9v7UXvnpA" - assertSuccess(try decode(jwt, algorithm: .hs512("secret".data(using: .utf8)!))) { payload in - XCTAssertEqual(payload as! [String: String], ["some": "payload"]) - } - } -} - -// MARK: Helpers - -func assertSuccess(_ decoder: @autoclosure () throws -> Payload, closure: ((Payload) -> Void)? = nil) { - do { - let payload = try decoder() - closure?(payload) - } catch { - XCTFail("Failed to decode while expecting success. \(error)") - } -} - -func assertFailure(_ decoder: @autoclosure () throws -> Payload, closure: ((InvalidToken) -> Void)? = nil) { - do { - _ = try decoder() - XCTFail("Decoding succeeded, expected a failure.") - } catch let error as InvalidToken { - closure?(error) - } catch { - XCTFail("Unexpected error") - } -} - -func assertDecodeError(_ decoder: @autoclosure () throws -> Payload, error: String) { - assertFailure(try decoder()) { failure in - switch failure { - case .decodeError(let decodeError): - if decodeError != error { - XCTFail("Incorrect decode error \(decodeError) != \(error)") - } - default: - XCTFail("Failure for the wrong reason \(failure)") - } - } -} diff --git a/Tests/JWTTests/PayloadTests.swift b/Tests/JWTTests/PayloadTests.swift new file mode 100644 index 0000000..150be11 --- /dev/null +++ b/Tests/JWTTests/PayloadTests.swift @@ -0,0 +1,54 @@ +import XCTest +import JWT + +class PayloadTests: XCTestCase { + func testIssuer() { + _ = JWT.encode(.none) { builder in + builder.issuer = "fuller.li" + XCTAssertEqual(builder.issuer, "fuller.li") + XCTAssertEqual(builder["iss"] as? String, "fuller.li") + } + } + + func testAudience() { + _ = JWT.encode(.none) { builder in + builder.audience = "cocoapods" + XCTAssertEqual(builder.audience, "cocoapods") + XCTAssertEqual(builder["aud"] as? String, "cocoapods") + } + } + + func testExpiration() { + _ = JWT.encode(.none) { builder in + let date = Date(timeIntervalSince1970: Date().timeIntervalSince1970) + builder.expiration = date + XCTAssertEqual(builder.expiration, date) + XCTAssertEqual(builder["exp"] as? TimeInterval, date.timeIntervalSince1970) + } + } + + func testNotBefore() { + _ = JWT.encode(.none) { builder in + let date = Date(timeIntervalSince1970: Date().timeIntervalSince1970) + builder.notBefore = date + XCTAssertEqual(builder.notBefore, date) + XCTAssertEqual(builder["nbf"] as? TimeInterval, date.timeIntervalSince1970) + } + } + + func testIssuedAt() { + _ = JWT.encode(.none) { builder in + let date = Date(timeIntervalSince1970: Date().timeIntervalSince1970) + builder.issuedAt = date + XCTAssertEqual(builder.issuedAt, date) + XCTAssertEqual(builder["iat"] as? TimeInterval, date.timeIntervalSince1970) + } + } + + func testCustomAttributes() { + _ = JWT.encode(.none) { builder in + builder["user"] = "kyle" + XCTAssertEqual(builder["user"] as? String, "kyle") + } + } +} diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 8488183..492555b 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -1,18 +1,51 @@ import XCTest +@testable import JWATests @testable import JWTTests +extension HMACAlgorithmTests { + static var allTests: [(String, (HMACAlgorithmTests) -> () throws -> Void)] { + return [ + ("testSHA256Name", testSHA256Name), + ("testSHA384Name", testSHA384Name), + ("testSHA512Name", testSHA512Name), + ("testSHA256Sign", testSHA256Sign), + ("testSHA384Sign", testSHA384Sign), + ("testSHA512Sign", testSHA512Sign), + ("testSHA256Verify", testSHA256Verify), + ("testSHA384Verify", testSHA384Verify), + ("testSHA512Verify", testSHA512Verify) + ] + } +} -extension EncodeTests { - static var allTests: [(String, (EncodeTests) -> (Void) throws -> Void)] { +extension NoneAlgorithmTests { + static var allTests: [(String, (NoneAlgorithmTests) -> () throws -> Void)] { return [ - ("testEncodingJWT", testEncodingJWT), - ("testEncodingWithBuilder", testEncodingWithBuilder), + ("testName", testName), + ("testSign", testSign), + ("testVerify", testVerify) ] } } +extension CompactJSONDecoderTests { + static var allTests: [(String, (CompactJSONDecoderTests) -> () throws -> Void)] { + return [ + ("testDecoder", testDecoder) + ] + } +} + +extension CompactJSONEncoderTests { + static var allTests: [(String, (CompactJSONEncoderTests) -> () throws -> Void)] { + return [ + ("testEncode", testEncode) + ] + } +} + extension DecodeTests { - static var allTests: [(String, (DecodeTests) -> (Void) throws -> Void)] { + static var allTests: [(String, (DecodeTests) -> () throws -> Void)] { return [ ("testDecodingValidJWT", testDecodingValidJWT), ("testFailsToDecodeInvalidStringWithoutThreeSegments", testFailsToDecodeInvalidStringWithoutThreeSegments), @@ -21,7 +54,7 @@ extension DecodeTests { ("testIncorrectIssuerValidation", testIncorrectIssuerValidation), ("testMissingIssuerValidation", testMissingIssuerValidation), ("testExpiredClaim", testExpiredClaim), - ("testInvalidExpiaryClaim", testInvalidExpiaryClaim), + ("testInvalidExpiryClaim", testInvalidExpiryClaim), ("testUnexpiredClaim", testUnexpiredClaim), ("testUnexpiredClaimString", testUnexpiredClaimString), ("testNotBeforeClaim", testNotBeforeClaim), @@ -40,26 +73,64 @@ extension DecodeTests { ("testNoneFailsWithSecretAlgorithm", testNoneFailsWithSecretAlgorithm), ("testMatchesAnyAlgorithm", testMatchesAnyAlgorithm), ("testHS384Algorithm", testHS384Algorithm), - ("testHS512Algorithm", testHS512Algorithm), + ("testHS512Algorithm", testHS512Algorithm) ] } } +extension IntegrationTests { + static var allTests: [(String, (IntegrationTests) -> () throws -> Void)] { + return [ + ("testVerificationFailureWithoutLeeway", testVerificationFailureWithoutLeeway), + ("testVerificationSuccessWithLeeway", testVerificationSuccessWithLeeway) + ] + } +} + +extension JWTEncodeTests { + static var allTests: [(String, (JWTEncodeTests) -> () throws -> Void)] { + return [ + ("testEncodingJWT", testEncodingJWT), + ("testEncodingWithBuilder", testEncodingWithBuilder), + ("testEncodingClaimsWithHeaders", testEncodingClaimsWithHeaders) + ] + } +} + extension PayloadTests { - static var allTests: [(String, (PayloadTests) -> (Void) throws -> Void)] { + static var allTests: [(String, (PayloadTests) -> () throws -> Void)] { return [ ("testIssuer", testIssuer), ("testAudience", testAudience), ("testExpiration", testExpiration), ("testNotBefore", testNotBefore), ("testIssuedAt", testIssuedAt), - ("testCustomAttributes", testCustomAttributes), + ("testCustomAttributes", testCustomAttributes) ] } } +extension ValidationTests { + static var allTests: [(String, (ValidationTests) -> () throws -> Void)] { + return [ + ("testClaimJustExpiredWithoutLeeway", testClaimJustExpiredWithoutLeeway), + ("testClaimJustNotExpiredWithoutLeeway", testClaimJustNotExpiredWithoutLeeway), + ("testNotBeforeIsImmatureSignatureWithoutLeeway", testNotBeforeIsImmatureSignatureWithoutLeeway), + ("testNotBeforeIsValidWithLeeway", testNotBeforeIsValidWithLeeway), + ("testIssuedAtIsInFutureWithoutLeeway", testIssuedAtIsInFutureWithoutLeeway), + ("testIssuedAtIsValidWithLeeway", testIssuedAtIsValidWithLeeway) + ] + } +} + XCTMain([ - testCase(EncodeTests.allTests), + testCase(HMACAlgorithmTests.allTests), + testCase(NoneAlgorithmTests.allTests), + testCase(CompactJSONDecoderTests.allTests), + testCase(CompactJSONEncoderTests.allTests), testCase(DecodeTests.allTests), + testCase(IntegrationTests.allTests), + testCase(JWTEncodeTests.allTests), testCase(PayloadTests.allTests), + testCase(ValidationTests.allTests) ])