├── .gitignore ├── JSONWebToken tvOS └── Info.plist ├── JSONWebToken tvOSTests └── Info.plist ├── JSONWebToken.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── JSONWebToken tvOS.xcscheme │ └── JSONWebToken.xcscheme ├── JSONWebToken ├── ClaimValidator.swift ├── Data+hash.swift ├── HMAC.swift ├── Info.plist ├── JSONWebToken.entitlements ├── JSONWebToken.h ├── JSONWebToken.swift ├── NSData+Base64URLEncoding.swift ├── RSASSA_PKCS1.swift ├── SecKeyUtils.swift ├── SignatureAlgorithm.swift ├── SignatureValidator.swift ├── TokenSigner.swift └── TokenValidator.swift ├── JSONWebTokenTests ├── ClaimTests.swift ├── DecodeTests.swift ├── ECDSATests.swift ├── GenerateTests.swift ├── HMACTests.swift ├── Info.plist ├── KeyUtilsTests.swift ├── RSASSA_PKCS1Tests.swift ├── RSASSA_PSSTests.swift ├── Samples │ ├── EC_privateKey │ ├── EC_publicKey.pub │ ├── ES256.jwt │ ├── ES384.jwt │ ├── ES512.jwt │ ├── GenerateSample.py │ ├── HS256.jwt │ ├── HS384.jwt │ ├── HS512.jwt │ ├── PS256.jwt │ ├── PS384.jwt │ ├── PS512.jwt │ ├── RS256.jwt │ ├── RS256_2.jwt │ ├── RS384.jwt │ ├── RS384_2.jwt │ ├── RS512.jwt │ ├── RS512_2.jwt │ ├── TestCertificate.cer │ ├── all_claim_valid_1.jwt │ ├── all_claim_valid_2.jwt │ ├── all_claim_valid_2_signed.jwt │ ├── empty.jwt │ ├── empty2.jwt │ ├── identity.p12 │ ├── invalid_alg.jwt │ ├── invalid_aud_format.jwt │ ├── invalid_exp_format.jwt │ ├── invalid_expired.jwt │ ├── invalid_header_base64.jwt │ ├── invalid_header_json.jwt │ ├── invalid_header_json_structure.jwt │ ├── invalid_iat_format.jwt │ ├── invalid_iss_format.jwt │ ├── invalid_jti_format.jwt │ ├── invalid_missing_alg.jwt │ ├── invalid_nbf_format.jwt │ ├── invalid_nbf_immature.jwt │ ├── invalid_payload_base64.jwt │ ├── invalid_payload_json.jwt │ ├── invalid_payload_json_structure.jwt │ ├── invalid_signature_base64.jwt │ ├── invalid_structure.jwt │ ├── invalid_structure_2.jwt │ ├── invalid_sub_format.jwt │ ├── invalid_typ.jwt │ ├── private.pem │ ├── private2.pem │ ├── private_key_pem_to_p12_identity.sh │ ├── public.exponent │ ├── public.modulus │ ├── public.pem │ ├── public2.pem │ └── valid_missing_typ.jwt └── TestUtils.swift ├── KTVJSONWebToken.podspec ├── LICENCE ├── README.md └── TestContainer ├── AppDelegate.swift ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist └── ViewController.swift /.gitignore: -------------------------------------------------------------------------------- 1 | JSONWebToken.xcodeproj/xcuserdata 2 | -------------------------------------------------------------------------------- /JSONWebToken tvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /JSONWebToken tvOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /JSONWebToken.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JSONWebToken.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /JSONWebToken.xcodeproj/xcshareddata/xcschemes/JSONWebToken tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 77 | 83 | 84 | 85 | 86 | 92 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /JSONWebToken.xcodeproj/xcshareddata/xcschemes/JSONWebToken.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 46 | 47 | 53 | 54 | 55 | 56 | 60 | 61 | 62 | 63 | 65 | 71 | 72 | 73 | 74 | 75 | 85 | 86 | 92 | 93 | 94 | 95 | 101 | 102 | 108 | 109 | 110 | 111 | 113 | 114 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /JSONWebToken/ClaimValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONWebToken 3 | // 4 | // Created by Antoine Palazzolo on 20/11/15. 5 | // 6 | 7 | import Foundation 8 | 9 | 10 | public struct ClaimValidatorError : Error { 11 | public let message : String 12 | public init(message : String) { 13 | self.message = message 14 | } 15 | } 16 | public func ClaimTransformString(_ value : Any) throws -> String { 17 | if let result = value as? String { 18 | return result 19 | } else { 20 | throw ClaimValidatorError(message: "\(value) is not a String type value") 21 | } 22 | } 23 | public func ClaimTransformDate(_ value : Any) throws -> Date { 24 | return try Date(timeIntervalSince1970: ClaimTransformNumber(value).doubleValue) 25 | } 26 | public func ClaimTransformNumber(_ value : Any) throws -> NSNumber { 27 | if let numberValue = value as? NSNumber { 28 | return numberValue 29 | } else { 30 | throw ClaimValidatorError(message: "\(value) is not a Number type value") 31 | } 32 | } 33 | public func ClaimTransformArray(_ elementTransform : (Any) throws -> U, value : Any) throws -> [U] { 34 | if let array = value as? NSArray { 35 | return try array.map(elementTransform) 36 | } else { 37 | throw ClaimValidatorError(message: "\(value) is not an Array type value") 38 | } 39 | } 40 | public struct ClaimValidator : JSONWebTokenValidatorType { 41 | fileprivate var isOptional : Bool = false 42 | fileprivate var validator : (T) -> ValidationResult = {_ in return .success} 43 | 44 | public let key : String 45 | public let transform : (Any) throws -> T 46 | 47 | public init(key : String, transform : @escaping (Any) throws -> T) { 48 | self.key = key 49 | self.transform = transform 50 | } 51 | 52 | public init(claim : JSONWebToken.Payload.RegisteredClaim, transform : @escaping (Any) throws -> T) { 53 | self.init(key : claim.rawValue,transform : transform) 54 | } 55 | 56 | public func withValidator(_ validator : @escaping (T) -> ValidationResult) -> ClaimValidator { 57 | var result = self 58 | result.validator = { input in 59 | let validationResult = self.validator(input) 60 | guard case ValidationResult.success = validationResult else { 61 | return validationResult 62 | } 63 | return validator(input) 64 | } 65 | return result 66 | } 67 | public func withValidator(_ validator : @escaping (T) -> Bool) -> ClaimValidator { 68 | return self.withValidator { 69 | return validator($0) ? .success : .failure(ClaimValidatorError(message: "custom validation failed for key \(self.key)")) 70 | } 71 | } 72 | 73 | 74 | public var optional : ClaimValidator { 75 | var result = self 76 | result.isOptional = true 77 | return result 78 | } 79 | 80 | 81 | public func validateToken(_ token : JSONWebToken) -> ValidationResult { 82 | guard let initialValue = token.payload[self.key] else { 83 | if self.isOptional { 84 | return .success 85 | } else { 86 | return .failure(ClaimValidatorError(message: "missing value for claim with key \(self.key)")) 87 | } 88 | } 89 | do { 90 | return try self.validator(self.transform(initialValue)) 91 | } catch { 92 | return .failure(error) 93 | } 94 | } 95 | } 96 | public struct RegisteredClaimValidator { 97 | 98 | public static let issuer = ClaimValidator(claim: .Issuer, transform: ClaimTransformString) 99 | public static let subject = ClaimValidator(claim: .Subject, transform: ClaimTransformString) 100 | public static let audience = ClaimValidator(claim: .Audience, transform: { value throws -> [String] in 101 | if let singleAudience = try? ClaimTransformString(value) { 102 | return [singleAudience] 103 | } else if let multiple = try? ClaimTransformArray(ClaimTransformString,value : value) { 104 | return multiple 105 | } else { 106 | throw ClaimValidatorError(message: "audience value \(value) is not an array or string value") 107 | } 108 | }) 109 | 110 | public static let expiration = ClaimValidator(claim: .ExpirationTime, transform: ClaimTransformDate).withValidator { date -> ValidationResult in 111 | if date.timeIntervalSinceNow >= 0.0 { 112 | return .success 113 | } else { 114 | return .failure(ClaimValidatorError(message: "token is expired")) 115 | } 116 | } 117 | public static let notBefore = ClaimValidator(claim: .NotBefore, transform: ClaimTransformDate).withValidator { date -> ValidationResult in 118 | if date.timeIntervalSinceNow <= 0.0 { 119 | return .success 120 | } else { 121 | return .failure(ClaimValidatorError(message: "token cannot be used before \(date)")) 122 | } 123 | } 124 | public static let issuedAt = ClaimValidator(claim: .IssuedAt, transform: ClaimTransformDate) 125 | public static let jwtIdentifier = ClaimValidator(claim: .JWTIdentifier, transform: ClaimTransformString) 126 | 127 | } 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /JSONWebToken/Data+hash.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data+hash.swift 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 19/05/2020. 6 | // 7 | 8 | import Foundation 9 | import CommonCrypto 10 | import CryptoKit 11 | 12 | extension Data { 13 | func sha(_ hashFunction: SignatureAlgorithm.HashFunction) -> Data { 14 | if #available(iOSApplicationExtension 13.0, *) { 15 | switch hashFunction { 16 | case .sha256: return Data(SHA256.hash(data: self)) 17 | case .sha384: return Data(SHA384.hash(data: self)) 18 | case .sha512: return Data(SHA512.hash(data: self)) 19 | } 20 | } else { 21 | return self.withUnsafeBytes { buffer -> Data in 22 | switch hashFunction { 23 | case .sha256: 24 | let result = UnsafeMutableBufferPointer.allocate(capacity: Int(CC_SHA256_DIGEST_LENGTH)) 25 | defer { result.deallocate() } 26 | CC_SHA256(buffer.baseAddress, CC_LONG(buffer.count), result.baseAddress) 27 | return Data(buffer: result) 28 | case .sha384: 29 | let result = UnsafeMutableBufferPointer.allocate(capacity: Int(CC_SHA384_DIGEST_LENGTH)) 30 | defer { result.deallocate() } 31 | CC_SHA384(buffer.baseAddress, CC_LONG(buffer.count), result.baseAddress) 32 | return Data(buffer: result) 33 | case .sha512: 34 | let result = UnsafeMutableBufferPointer.allocate(capacity: Int(CC_SHA512_DIGEST_LENGTH)) 35 | defer { result.deallocate() } 36 | CC_SHA512(buffer.baseAddress, CC_LONG(buffer.count), result.baseAddress) 37 | return Data(buffer: result) 38 | } 39 | } 40 | } 41 | } 42 | func hmac(_ hashFunction: SignatureAlgorithm.HashFunction, secret: Data) -> Data { 43 | if #available(iOSApplicationExtension 13.0, *) { 44 | let key = SymmetricKey(data: secret) 45 | switch hashFunction { 46 | case .sha256: 47 | return Data(HMAC.authenticationCode(for: self, using: key)) 48 | case .sha384: 49 | return Data(HMAC.authenticationCode(for: self, using: key)) 50 | case .sha512: 51 | return Data(HMAC.authenticationCode(for: self, using: key)) 52 | } 53 | } else { 54 | return self.withUnsafeBytes { buffer -> Data in 55 | return secret.withUnsafeBytes { secretBuffer -> Data in 56 | let function: CCHmacAlgorithm 57 | let resultLen: Int32 58 | switch hashFunction { 59 | case .sha256: 60 | function = CCHmacAlgorithm(kCCHmacAlgSHA256) 61 | resultLen = CC_SHA256_DIGEST_LENGTH 62 | case .sha384: 63 | function = CCHmacAlgorithm(kCCHmacAlgSHA384) 64 | resultLen = CC_SHA384_DIGEST_LENGTH 65 | case .sha512: 66 | function = CCHmacAlgorithm(kCCHmacAlgSHA512) 67 | resultLen = CC_SHA512_DIGEST_LENGTH 68 | } 69 | let result = UnsafeMutableBufferPointer.allocate(capacity: Int(resultLen)) 70 | defer { result.deallocate() } 71 | CCHmac(function, secretBuffer.baseAddress, secretBuffer.count, buffer.baseAddress, buffer.count, result.baseAddress) 72 | return Data(buffer: result) 73 | } 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /JSONWebToken/HMAC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HMAC.swift 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 18/11/15. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | public struct HMACSignature : SignatureValidator,TokenSigner { 12 | let secret : Data 13 | let hashFunction : SignatureAlgorithm.HashFunction 14 | public init(secret : Data,hashFunction : SignatureAlgorithm.HashFunction) { 15 | self.secret = secret 16 | self.hashFunction = hashFunction 17 | } 18 | public func canVerifyWithSignatureAlgorithm(_ alg : SignatureAlgorithm) -> Bool { 19 | if case SignatureAlgorithm.hmac(self.hashFunction) = alg { 20 | return true 21 | } 22 | return false 23 | } 24 | public func verify(_ input : Data, signature : Data) -> Bool { 25 | return input.hmac(self.hashFunction, secret: secret) == signature 26 | } 27 | 28 | public var signatureAlgorithm : SignatureAlgorithm { 29 | return .hmac(self.hashFunction) 30 | } 31 | public func sign(_ input : Data) throws -> Data { 32 | return input.hmac(self.hashFunction, secret: secret) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /JSONWebToken/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 2.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /JSONWebToken/JSONWebToken.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | get-task-allow 6 | 7 | 8 | -------------------------------------------------------------------------------- /JSONWebToken/JSONWebToken.h: -------------------------------------------------------------------------------- 1 | // 2 | // JSONWebToken.h 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 17/11/15. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for JSONWebToken. 11 | FOUNDATION_EXPORT double JSONWebTokenVersionNumber; 12 | 13 | //! Project version string for JSONWebToken. 14 | FOUNDATION_EXPORT const unsigned char JSONWebTokenVersionString[]; 15 | -------------------------------------------------------------------------------- /JSONWebToken/JSONWebToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONWebToken.swift 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 17/11/15. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct JSONWebToken { 11 | 12 | public indirect enum Error : Swift.Error { 13 | case badTokenStructure 14 | case cannotDecodeBase64Part(JSONWebToken.Part,String) 15 | case invalidJSON(JSONWebToken.Part,Swift.Error) 16 | case invalidJSONStructure(JSONWebToken.Part) 17 | case typeIsNotAJSONWebToken 18 | case invalidSignatureAlgorithm(String) 19 | case missingSignatureAlgorithm 20 | 21 | } 22 | public enum Part { 23 | case header 24 | case payload 25 | case signature 26 | } 27 | 28 | public struct Payload { 29 | public enum RegisteredClaim : String { 30 | case Issuer = "iss" 31 | case Subject = "sub" 32 | case Audience = "aud" 33 | case ExpirationTime = "exp" 34 | case NotBefore = "nbf" 35 | case IssuedAt = "iat" 36 | case JWTIdentifier = "jti" 37 | } 38 | 39 | var jsonPayload : [String : Any] 40 | fileprivate init(jsonPayload : [String : Any]) { 41 | self.jsonPayload = jsonPayload 42 | } 43 | public init() { 44 | jsonPayload = Dictionary() 45 | } 46 | public subscript(key : String) -> Any? { 47 | get { 48 | let result = jsonPayload[key] 49 | switch result { 50 | case .some(let value) where value is NSNull: 51 | return nil 52 | case .some(_): 53 | return result 54 | case .none: 55 | return nil 56 | } 57 | } 58 | set { 59 | if newValue == nil || newValue is NSNull { 60 | jsonPayload.removeValue(forKey: key) 61 | } else { 62 | jsonPayload[key] = newValue 63 | } 64 | } 65 | } 66 | fileprivate subscript(registeredClaim : RegisteredClaim) -> Any? { 67 | get { 68 | return self[registeredClaim.rawValue] 69 | } 70 | set { 71 | return self[registeredClaim.rawValue] = newValue 72 | } 73 | } 74 | public var issuer : String? { 75 | get { 76 | return (((try? self[.Issuer].map(RegisteredClaimValidator.issuer.transform)) as String??)) ?? nil 77 | } 78 | set { 79 | self[.Issuer] = newValue 80 | } 81 | } 82 | public var subject : String? { 83 | get { 84 | return (((try? self[.Subject].map(RegisteredClaimValidator.subject.transform)) as String??)) ?? nil 85 | } 86 | set { 87 | self[.Subject] = newValue 88 | } 89 | } 90 | public var audience : [String] { 91 | get { 92 | return (try? self[.Audience].map(RegisteredClaimValidator.audience.transform) ?? []) ?? [] 93 | } 94 | set { 95 | switch newValue.count { 96 | case 0: 97 | self[.Audience] = nil 98 | case 1: 99 | self[.Audience] = newValue[0] 100 | default: 101 | self[.Audience] = newValue 102 | } 103 | } 104 | } 105 | fileprivate static func jsonClaimValueFromDate(_ date : Date?) -> NSNumber? { 106 | return date.map { NSNumber(value: Int64($0.timeIntervalSince1970)) } 107 | } 108 | public var expiration : Date? { 109 | get { 110 | return (((try? self[.ExpirationTime].map(RegisteredClaimValidator.expiration.transform)) as Date??)) ?? nil 111 | } 112 | set { 113 | self[.ExpirationTime] = Payload.jsonClaimValueFromDate(newValue) 114 | } 115 | } 116 | 117 | public var notBefore : Date? { 118 | get { 119 | return (((try? self[.NotBefore].map(RegisteredClaimValidator.notBefore.transform)) as Date??)) ?? nil 120 | } 121 | set { 122 | self[.NotBefore] = Payload.jsonClaimValueFromDate(newValue) 123 | } 124 | } 125 | 126 | public var issuedAt : Date? { 127 | get { 128 | return (((try? self[.IssuedAt].map(RegisteredClaimValidator.issuedAt.transform)) as Date??)) ?? nil 129 | } 130 | set { 131 | self[.IssuedAt] = Payload.jsonClaimValueFromDate(newValue) 132 | } 133 | } 134 | public var jwtIdentifier : String? { 135 | get { 136 | return (((try? self[.JWTIdentifier].map(RegisteredClaimValidator.jwtIdentifier.transform)) as String??)) ?? nil 137 | } 138 | set { 139 | self[.JWTIdentifier] = newValue 140 | } 141 | } 142 | } 143 | 144 | 145 | public let signatureAlgorithm : SignatureAlgorithm 146 | public let payload : Payload 147 | let base64Parts : (header : String,payload : String, signature : String) 148 | 149 | public init(string input: String) throws { 150 | 151 | let parts = input.components(separatedBy: ".") 152 | guard parts.count == 3 else { throw Error.badTokenStructure } 153 | 154 | self.base64Parts = (parts[0],parts[1],parts[2]) 155 | 156 | guard let headerData = Data(jwt_base64URLEncodedString: base64Parts.header, options: []) else { 157 | throw Error.cannotDecodeBase64Part(.header,base64Parts.header) 158 | } 159 | guard let payloadData = Data(jwt_base64URLEncodedString: base64Parts.payload, options: []) else { 160 | throw Error.cannotDecodeBase64Part(.payload,base64Parts.payload) 161 | } 162 | guard Data(jwt_base64URLEncodedString: base64Parts.signature, options: []) != nil else { 163 | throw Error.cannotDecodeBase64Part(.signature,base64Parts.signature) 164 | } 165 | 166 | let jsonHeader = try JSONWebToken.jwtJSONFromData(headerData,part: .header) 167 | 168 | guard (jsonHeader["typ"] as? String).map({$0.uppercased() == "JWT"}) ?? true else { 169 | throw Error.typeIsNotAJSONWebToken 170 | } 171 | guard let signatureAlgorithm = try (jsonHeader["alg"] as? String).map(SignatureAlgorithm.init) else { 172 | throw Error.missingSignatureAlgorithm 173 | } 174 | self.signatureAlgorithm = signatureAlgorithm 175 | 176 | let jsonPayload = try JSONWebToken.jwtJSONFromData(payloadData,part: .payload) 177 | 178 | self.payload = Payload(jsonPayload: jsonPayload) 179 | } 180 | fileprivate static func jwtJSONFromData(_ data : Data, part : JSONWebToken.Part) throws -> [String : Any] { 181 | let json : Any 182 | do { 183 | json = try JSONSerialization.jsonObject(with: data, options: []) 184 | } catch { 185 | throw Error.invalidJSON(part,error) 186 | } 187 | guard let result = json as? [String : Any] else { 188 | throw Error.invalidJSONStructure(part) 189 | } 190 | return result 191 | } 192 | 193 | public init(payload : Payload, signer : TokenSigner? = nil) throws { 194 | self.signatureAlgorithm = signer?.signatureAlgorithm ?? SignatureAlgorithm.none 195 | self.payload = payload 196 | 197 | let header = ["alg" : self.signatureAlgorithm.jwtIdentifier , "typ" : "JWT"] 198 | let headerBase64 = try JSONSerialization.data(withJSONObject: header, options: []).jwt_base64URLEncodedStringWithOptions([]) 199 | let payloadBase64 = try JSONSerialization.data(withJSONObject: payload.jsonPayload, options: []).jwt_base64URLEncodedStringWithOptions([]) 200 | 201 | let signatureInput = headerBase64 + "." + payloadBase64 202 | 203 | let signature = try signer.map { 204 | try $0.sign(signatureInput.data(using: String.Encoding.utf8)!) 205 | } ?? Data() 206 | 207 | let signatureBase64 = signature.jwt_base64URLEncodedStringWithOptions([]) 208 | 209 | self.base64Parts = (headerBase64,payloadBase64,signatureBase64) 210 | } 211 | 212 | 213 | public func decodedDataForPart(_ part : Part) -> Data { 214 | switch part { 215 | case .header: 216 | return Data(jwt_base64URLEncodedString: base64Parts.header, options: [])! 217 | case .payload: 218 | return Data(jwt_base64URLEncodedString: base64Parts.payload, options: [])! 219 | case .signature: 220 | return Data(jwt_base64URLEncodedString: base64Parts.signature, options: [])! 221 | } 222 | } 223 | 224 | public var rawString : String { 225 | return "\(base64Parts.header).\(base64Parts.payload).\(base64Parts.signature)" 226 | } 227 | public var rawData : Data { 228 | return self.rawString.data(using: String.Encoding.utf8)! 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /JSONWebToken/NSData+Base64URLEncoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+Base64URLEncoding.swift 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 17/11/15. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Data { 11 | init?(jwt_base64URLEncodedString base64URLEncodedString: String, options : Data.Base64DecodingOptions) { 12 | let input = NSMutableString(string: base64URLEncodedString) 13 | input.replaceOccurrences(of: "-",with: "+", 14 | options: [.literal], 15 | range: NSRange(location: 0,length: input.length) 16 | ) 17 | input.replaceOccurrences(of: "_",with: "/", 18 | options: [.literal], 19 | range: NSRange(location: 0,length: input.length) 20 | ) 21 | switch (input.length % 4) 22 | { 23 | case 0: 24 | break 25 | case 1: 26 | input.append("==="); 27 | case 2: 28 | input.append("=="); 29 | case 3: 30 | input.append("="); 31 | default: 32 | fatalError("unreachable") 33 | } 34 | if let decoded = Data(base64Encoded: input as String, options: options){ 35 | self = decoded 36 | } else { 37 | return nil 38 | } 39 | } 40 | func jwt_base64URLEncodedStringWithOptions(_ options: NSData.Base64EncodingOptions) -> String { 41 | let result = NSMutableString(string: self.base64EncodedString(options: options)) 42 | result.replaceOccurrences(of: "+",with: "-", 43 | options: [.literal], 44 | range: NSRange(location: 0,length: result.length) 45 | ) 46 | result.replaceOccurrences(of: "/",with: "_", 47 | options: [.literal], 48 | range: NSRange(location: 0,length: result.length) 49 | ) 50 | result.replaceOccurrences(of: "=",with: "", 51 | options: [.literal], 52 | range: NSRange(location: 0,length: result.length) 53 | ) 54 | return result as String 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /JSONWebToken/RSASSA_PKCS1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RSASSA_PKCS1.swift 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 18/11/15. 6 | // 7 | 8 | import Foundation 9 | import Security 10 | 11 | private func paddingForHashFunction(_ f : SignatureAlgorithm.HashFunction) -> SecPadding { 12 | switch f { 13 | case .sha256: 14 | return SecPadding.PKCS1SHA256 15 | case .sha384: 16 | return SecPadding.PKCS1SHA384 17 | case .sha512: 18 | return SecPadding.PKCS1SHA512 19 | } 20 | } 21 | 22 | 23 | 24 | public struct RSAKey { 25 | enum Error : Swift.Error { 26 | case securityError(OSStatus) 27 | case publicKeyNotFoundInCertificate 28 | case cannotCreateCertificateFromData 29 | case invalidP12ImportResult 30 | case invalidP12NoIdentityFound 31 | } 32 | let value : SecKey 33 | 34 | public init(secKey :SecKey) { 35 | self.value = secKey 36 | } 37 | public init(secCertificate cert: SecCertificate) throws { 38 | var trust : SecTrust? = nil 39 | let result = SecTrustCreateWithCertificates(cert, nil, &trust) 40 | if result == errSecSuccess && trust != nil { 41 | if let publicKey = SecTrustCopyPublicKey(trust!) { 42 | self.init(secKey : publicKey) 43 | } else { 44 | throw Error.publicKeyNotFoundInCertificate 45 | } 46 | } else { 47 | throw Error.securityError(result) 48 | } 49 | } 50 | //Creates a certificate object from a DER representation of a certificate. 51 | public init(certificateData data: Data) throws { 52 | if let cert = SecCertificateCreateWithData(nil, data as CFData) { 53 | try self.init(secCertificate : cert) 54 | } else { 55 | throw Error.cannotCreateCertificateFromData 56 | } 57 | } 58 | 59 | public static func keysFromPkcs12Identity(_ p12Data : Data, passphrase : String) throws -> (publicKey : RSAKey, privateKey : RSAKey) { 60 | 61 | var importResult : CFArray? = nil 62 | let importParam = [kSecImportExportPassphrase as String: passphrase] 63 | let status = SecPKCS12Import(p12Data as CFData,importParam as CFDictionary, &importResult) 64 | 65 | guard status == errSecSuccess else { throw Error.securityError(status) } 66 | 67 | if let array = importResult.map({unsafeBitCast($0,to: NSArray.self)}), 68 | let content = array.firstObject as? NSDictionary, 69 | let identity = (content[kSecImportItemIdentity as String] as! SecIdentity?) 70 | { 71 | var privateKey : SecKey? = nil 72 | var certificate : SecCertificate? = nil 73 | let status = ( 74 | SecIdentityCopyPrivateKey(identity, &privateKey), 75 | SecIdentityCopyCertificate(identity, &certificate) 76 | ) 77 | guard status.0 == errSecSuccess else { throw Error.securityError(status.0) } 78 | guard status.1 == errSecSuccess else { throw Error.securityError(status.1) } 79 | if privateKey != nil && certificate != nil { 80 | return try (RSAKey(secCertificate: certificate!),RSAKey(secKey: privateKey!)) 81 | } else { 82 | throw Error.invalidP12ImportResult 83 | } 84 | } else { 85 | throw Error.invalidP12NoIdentityFound 86 | } 87 | } 88 | } 89 | 90 | public struct RSAPKCS1Verifier : SignatureValidator { 91 | let hashFunction : SignatureAlgorithm.HashFunction 92 | let key : RSAKey 93 | 94 | public init(key : RSAKey, hashFunction : SignatureAlgorithm.HashFunction) { 95 | self.hashFunction = hashFunction 96 | self.key = key 97 | } 98 | public func canVerifyWithSignatureAlgorithm(_ alg : SignatureAlgorithm) -> Bool { 99 | if case SignatureAlgorithm.rsassa_PKCS1(self.hashFunction) = alg { 100 | return true 101 | } 102 | return false 103 | } 104 | public func verify(_ input : Data, signature : Data) -> Bool { 105 | let signedDataHash = input.sha(self.hashFunction) 106 | let padding = paddingForHashFunction(self.hashFunction) 107 | 108 | let result = signature.withUnsafeBytes { signatureRawPointer in 109 | signedDataHash.withUnsafeBytes { signedHashRawPointer in 110 | SecKeyRawVerify( 111 | key.value, 112 | padding, 113 | signedHashRawPointer, 114 | signedDataHash.count, 115 | signatureRawPointer, 116 | signature.count 117 | ) 118 | } 119 | } 120 | 121 | switch result { 122 | case errSecSuccess: 123 | return true 124 | default: 125 | return false 126 | } 127 | } 128 | } 129 | 130 | public struct RSAPKCS1Signer : TokenSigner { 131 | enum Error : Swift.Error { 132 | case securityError(OSStatus) 133 | } 134 | 135 | let hashFunction : SignatureAlgorithm.HashFunction 136 | let key : RSAKey 137 | 138 | public init(hashFunction : SignatureAlgorithm.HashFunction, key : RSAKey) { 139 | self.hashFunction = hashFunction 140 | self.key = key 141 | } 142 | 143 | public var signatureAlgorithm : SignatureAlgorithm { 144 | return .rsassa_PKCS1(self.hashFunction) 145 | } 146 | 147 | public func sign(_ input : Data) throws -> Data { 148 | let signedDataHash = input.sha(self.hashFunction) 149 | let padding = paddingForHashFunction(self.hashFunction) 150 | 151 | var result = Data(count: SecKeyGetBlockSize(self.key.value)) 152 | var resultSize = result.count 153 | let status = result.withUnsafeMutableBytes { resultBytes in 154 | SecKeyRawSign(key.value, padding, (signedDataHash as NSData).bytes.bindMemory(to: UInt8.self, capacity: signedDataHash.count), signedDataHash.count, UnsafeMutablePointer(resultBytes), &resultSize) 155 | } 156 | 157 | switch status { 158 | case errSecSuccess: 159 | return result.subdata(in: 0.. RSAKey { 57 | let key : SecKey? = try { 58 | if let existingData = try getKeyData(tag) { 59 | let newData = keyData.dataByStrippingX509Header() 60 | if existingData != newData { 61 | try updateKey(tag, data: newData) 62 | } 63 | return try getKey(tag) 64 | } else { 65 | return try addKey(tag, data: keyData.dataByStrippingX509Header()) 66 | } 67 | }() 68 | if let result = key { 69 | return RSAKey(secKey : result) 70 | } else { 71 | throw KeyUtilError.badKeyFormat 72 | } 73 | } 74 | @discardableResult static func registerOrUpdateKey(modulus: Data, exponent : Data, tag : String) throws -> RSAKey { 75 | let combinedData = Data(modulus: modulus, exponent: exponent) 76 | return try RSAKey.registerOrUpdateKey(combinedData, tag : tag) 77 | } 78 | @discardableResult static func registerOrUpdatePublicPEMKey(_ keyData : Data, tag : String) throws -> RSAKey { 79 | guard let stringValue = String(data: keyData, encoding: String.Encoding.utf8) else { 80 | throw KeyUtilError.notStringReadable 81 | } 82 | 83 | let base64Content : String = try { 84 | //remove ----BEGIN and ----END 85 | let scanner = Scanner(string: stringValue) 86 | scanner.charactersToBeSkipped = CharacterSet.whitespacesAndNewlines 87 | if scanner.scanString("-----BEGIN", into: nil) { 88 | scanner.scanUpTo("KEY-----", into: nil) 89 | guard scanner.scanString("KEY-----", into: nil) else { 90 | throw KeyUtilError.badPEMArmor 91 | } 92 | 93 | var content : NSString? = nil 94 | scanner.scanUpTo("-----END", into: &content) 95 | guard scanner.scanString("-----END", into: nil) else { 96 | throw KeyUtilError.badPEMArmor 97 | } 98 | return content?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) 99 | } 100 | return nil 101 | }() ?? stringValue 102 | 103 | guard let decodedKeyData = Data(base64Encoded: base64Content, options:[.ignoreUnknownCharacters]) else { 104 | throw KeyUtilError.notBase64Readable 105 | } 106 | return try RSAKey.registerOrUpdateKey(decodedKeyData, tag: tag) 107 | } 108 | static func registeredKeyWithTag(_ tag : String) -> RSAKey? { 109 | return ((((try? getKey(tag)) as SecKey??)) ?? nil).map(RSAKey.init) 110 | } 111 | static func removeKeyWithTag(_ tag : String) { 112 | do { 113 | try deleteKey(tag) 114 | } catch {} 115 | } 116 | } 117 | 118 | private func getKey(_ tag: String) throws -> SecKey? { 119 | var keyRef: AnyObject? 120 | 121 | var query = matchQueryWithTag(tag) 122 | query[kSecReturnRef as String] = kCFBooleanTrue 123 | 124 | let status = SecItemCopyMatching(query as CFDictionary, &keyRef) 125 | 126 | switch status { 127 | case errSecSuccess: 128 | if keyRef != nil { 129 | return (keyRef as! SecKey) 130 | } else { 131 | return nil 132 | } 133 | case errSecItemNotFound: 134 | return nil 135 | default: 136 | throw RSAKey.Error.securityError(status) 137 | } 138 | } 139 | internal func getKeyData(_ tag: String) throws -> Data? { 140 | 141 | var query = matchQueryWithTag(tag) 142 | query[kSecReturnData as String] = kCFBooleanTrue 143 | 144 | var result: AnyObject? = nil 145 | let status = SecItemCopyMatching(query as CFDictionary, &result) 146 | 147 | switch status { 148 | case errSecSuccess: 149 | return (result as! Data) 150 | case errSecItemNotFound: 151 | return nil 152 | default: 153 | throw RSAKey.Error.securityError(status) 154 | } 155 | } 156 | private func updateKey(_ tag: String, data: Data) throws { 157 | let query = matchQueryWithTag(tag) 158 | let updateParam = [kSecValueData as String : data] 159 | let status = SecItemUpdate(query as CFDictionary, updateParam as CFDictionary) 160 | guard status == errSecSuccess else { 161 | throw RSAKey.Error.securityError(status) 162 | } 163 | } 164 | 165 | private func deleteKey(_ tag: String) throws { 166 | let query = matchQueryWithTag(tag) 167 | let status = SecItemDelete(query as CFDictionary) 168 | if status != errSecSuccess { 169 | throw RSAKey.Error.securityError(status) 170 | } 171 | } 172 | private func matchQueryWithTag(_ tag : String) -> Dictionary { 173 | return [ 174 | kSecAttrKeyType as String : kSecAttrKeyTypeRSA, 175 | kSecClass as String : kSecClassKey, 176 | kSecAttrApplicationTag as String : tag, 177 | ] 178 | } 179 | 180 | private func addKey(_ tag: String, data: Data) throws -> SecKey? { 181 | var publicAttributes = Dictionary() 182 | publicAttributes[kSecAttrKeyType as String] = kSecAttrKeyTypeRSA 183 | publicAttributes[kSecClass as String] = kSecClassKey 184 | publicAttributes[kSecAttrApplicationTag as String] = tag as CFString 185 | publicAttributes[kSecValueData as String] = data as CFData 186 | publicAttributes[kSecReturnPersistentRef as String] = kCFBooleanTrue 187 | 188 | var persistentRef: CFTypeRef? 189 | let status = SecItemAdd(publicAttributes as CFDictionary, &persistentRef) 190 | if status == noErr || status == errSecDuplicateItem { 191 | return try getKey(tag) 192 | } 193 | throw RSAKey.Error.securityError(status) 194 | } 195 | 196 | /// 197 | /// Encoding/Decoding lengths as octets 198 | /// 199 | private extension NSInteger { 200 | func encodedOctets() -> [CUnsignedChar] { 201 | // Short form 202 | if self < 128 { 203 | return [CUnsignedChar(self)]; 204 | } 205 | 206 | // Long form 207 | let i = (self / 256) + 1 208 | var len = self 209 | var result: [CUnsignedChar] = [CUnsignedChar(i + 0x80)] 210 | 211 | for _ in 0..> 8 214 | } 215 | 216 | return result 217 | } 218 | 219 | init?(octetBytes: [CUnsignedChar], startIdx: inout NSInteger) { 220 | if octetBytes[startIdx] < 128 { 221 | // Short form 222 | self.init(octetBytes[startIdx]) 223 | startIdx += 1 224 | } else { 225 | // Long form 226 | let octets = NSInteger(octetBytes[startIdx] - CUnsignedChar(128)) 227 | 228 | if octets > octetBytes.count - startIdx { 229 | self.init(0) 230 | return nil 231 | } 232 | 233 | var result = UInt64(0) 234 | 235 | for j in 1...octets { 236 | result = (result << 8) 237 | result = result + UInt64(octetBytes[startIdx + j]) 238 | } 239 | 240 | startIdx += 1 + octets 241 | self.init(result) 242 | } 243 | } 244 | } 245 | 246 | /// 247 | /// Manipulating data 248 | /// 249 | private extension Data { 250 | init(modulus: Data, exponent: Data) { 251 | // Make sure neither the modulus nor the exponent start with a null byte 252 | var modulusBytes = [CUnsignedChar](UnsafeBufferPointer(start: (modulus as NSData).bytes.bindMemory(to: CUnsignedChar.self, capacity: modulus.count), count: modulus.count / MemoryLayout.size)) 253 | let exponentBytes = [CUnsignedChar](UnsafeBufferPointer(start: (exponent as NSData).bytes.bindMemory(to: CUnsignedChar.self, capacity: exponent.count), count: exponent.count / MemoryLayout.size)) 254 | 255 | // Make sure modulus starts with a 0x00 256 | if let prefix = modulusBytes.first , prefix != 0x00 { 257 | modulusBytes.insert(0x00, at: 0) 258 | } 259 | 260 | // Lengths 261 | let modulusLengthOctets = modulusBytes.count.encodedOctets() 262 | let exponentLengthOctets = exponentBytes.count.encodedOctets() 263 | 264 | // Total length is the sum of components + types 265 | let totalLengthOctets = (modulusLengthOctets.count + modulusBytes.count + exponentLengthOctets.count + exponentBytes.count + 2).encodedOctets() 266 | 267 | // Combine the two sets of data into a single container 268 | var builder: [CUnsignedChar] = [] 269 | let data = NSMutableData() 270 | 271 | // Container type and size 272 | builder.append(0x30) 273 | builder.append(contentsOf: totalLengthOctets) 274 | data.append(builder, length: builder.count) 275 | builder.removeAll(keepingCapacity: false) 276 | 277 | // Modulus 278 | builder.append(0x02) 279 | builder.append(contentsOf: modulusLengthOctets) 280 | data.append(builder, length: builder.count) 281 | builder.removeAll(keepingCapacity: false) 282 | data.append(modulusBytes, length: modulusBytes.count) 283 | 284 | // Exponent 285 | builder.append(0x02) 286 | builder.append(contentsOf: exponentLengthOctets) 287 | data.append(builder, length: builder.count) 288 | data.append(exponentBytes, length: exponentBytes.count) 289 | 290 | self = Data(referencing: data) 291 | } 292 | 293 | 294 | func dataByStrippingX509Header() -> Data { 295 | 296 | var bytes = [CUnsignedChar](repeating: 0, count: self.count) 297 | (self as NSData).getBytes(&bytes, length:self.count) 298 | 299 | var range = NSRange(location: 0, length: self.count) 300 | var offset = 0 301 | 302 | // ASN.1 Sequence 303 | if bytes[offset] == 0x30 { 304 | offset += 1 305 | 306 | // Skip over length 307 | let _ = NSInteger(octetBytes: bytes, startIdx: &offset) 308 | 309 | let OID: [CUnsignedChar] = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 310 | 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00] 311 | let slice: [CUnsignedChar] = Array(bytes[offset..<(offset + OID.count)]) 312 | 313 | if slice == OID { 314 | offset += OID.count 315 | 316 | // Type 317 | if bytes[offset] != 0x03 { 318 | return self 319 | } 320 | 321 | offset += 1 322 | 323 | // Skip over the contents length field 324 | let _ = NSInteger(octetBytes: bytes, startIdx: &offset) 325 | 326 | // Contents should be separated by a null from the header 327 | if bytes[offset] != 0x00 { 328 | return self 329 | } 330 | 331 | offset += 1 332 | range.location += offset 333 | range.length -= offset 334 | } else { 335 | return self 336 | } 337 | } 338 | 339 | return self.subdata(in : Range(range)!) 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /JSONWebToken/SignatureAlgorithm.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Signature.swift 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 18/11/15. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | public enum SignatureAlgorithm { 12 | public enum HashFunction : Int { 13 | case sha256 = 256 14 | case sha384 = 384 15 | case sha512 = 512 16 | 17 | fileprivate var jwtIdentifierSuffix : String { 18 | switch self { 19 | case .sha256: 20 | return "256" 21 | case .sha384: 22 | return "384" 23 | case .sha512: 24 | return "512" 25 | } 26 | } 27 | } 28 | 29 | case none 30 | case hmac(HashFunction) // HMAC -> HSXXX 31 | case rsassa_PKCS1(HashFunction) // RSASSA-PKCS1-v1_5 -> RSXXX 32 | case ecdsa(HashFunction) // ECDSA -> ESXXX 33 | case rsassa_PSS(HashFunction) //RSASSA-PSS -> PSXXX 34 | 35 | public init(name : String) throws { 36 | guard name.count > 0 else {throw JSONWebToken.Error.invalidSignatureAlgorithm(name)} 37 | guard name.lowercased() != "none" else { self = .none; return } 38 | 39 | let prefixIndex = name.index(name.startIndex, offsetBy: 2) 40 | let prefix = String(name[.. Bool 11 | func verify(_ input : Data, signature : Data) -> Bool 12 | } 13 | public enum SignatureValidatorError : Error { 14 | case algorithmMismatch 15 | case badInputData 16 | case signatureMismatch 17 | } 18 | extension SignatureValidator { 19 | public func validateToken(_ token : JSONWebToken) -> ValidationResult { 20 | guard self.canVerifyWithSignatureAlgorithm(token.signatureAlgorithm) else { 21 | return .failure(SignatureValidatorError.algorithmMismatch) 22 | } 23 | guard let input = (token.base64Parts.header+"."+token.base64Parts.payload).data(using: String.Encoding.utf8) else { 24 | return .failure(SignatureValidatorError.badInputData) 25 | } 26 | guard self.verify(input, signature: token.decodedDataForPart(.signature)) else { 27 | return .failure(SignatureValidatorError.signatureMismatch) 28 | } 29 | return .success 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /JSONWebToken/TokenSigner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONWebToken 3 | // 4 | // Created by Antoine Palazzolo on 23/11/15. 5 | // 6 | 7 | import Foundation 8 | 9 | 10 | public protocol TokenSigner { 11 | var signatureAlgorithm : SignatureAlgorithm {get} 12 | func sign(_ input : Data) throws -> Data 13 | } 14 | -------------------------------------------------------------------------------- /JSONWebToken/TokenValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TokenValidator.swift 3 | // 4 | // Created by Antoine Palazzolo on 23/11/15. 5 | // 6 | 7 | import Foundation 8 | 9 | public enum ValidationResult { 10 | case success 11 | case failure(Error) 12 | 13 | public var isValid : Bool { 14 | if case .success = self { 15 | return true 16 | } 17 | return false 18 | } 19 | } 20 | public protocol JSONWebTokenValidatorType { 21 | func validateToken(_ token : JSONWebToken) -> ValidationResult 22 | } 23 | public struct JSONWebTokenValidator : JSONWebTokenValidatorType { 24 | fileprivate let validator : (_ token : JSONWebToken) -> ValidationResult 25 | 26 | public func validateToken(_ token : JSONWebToken) -> ValidationResult { 27 | return self.validator(token) 28 | } 29 | } 30 | 31 | public struct CombinedValidatorError : Error { 32 | public let errors : [Error] 33 | } 34 | 35 | public func &(lhs : JSONWebTokenValidatorType, rhs : JSONWebTokenValidatorType) -> JSONWebTokenValidatorType { 36 | let and = { (token : JSONWebToken) -> ValidationResult in 37 | let errors = [lhs,rhs].map{ $0.validateToken(token) }.map { validation -> Error? in 38 | if case ValidationResult.failure(let error) = validation { 39 | return Optional.some(error) 40 | } else { 41 | return nil 42 | } 43 | }.compactMap {$0} 44 | return errors.count > 0 ? .failure(CombinedValidatorError(errors: errors)) : .success 45 | } 46 | return JSONWebTokenValidator(validator: and) 47 | 48 | } 49 | 50 | public func |(lhs : JSONWebTokenValidatorType, rhs : JSONWebTokenValidatorType) -> JSONWebTokenValidatorType { 51 | let or = { (token : JSONWebToken) -> ValidationResult in 52 | var errors = [Error]() 53 | for validator in [lhs,rhs] { 54 | switch validator.validateToken(token) { 55 | case .success: 56 | return .success 57 | case .failure(let error): 58 | errors.append(error) 59 | } 60 | } 61 | return .failure(CombinedValidatorError(errors: errors)) 62 | 63 | } 64 | return JSONWebTokenValidator(validator: or) 65 | } 66 | -------------------------------------------------------------------------------- /JSONWebTokenTests/ClaimTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClaimTests.swift 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 24/11/15. 6 | // 7 | 8 | import Foundation 9 | 10 | import JSONWebToken 11 | import XCTest 12 | 13 | class ClaimTests : XCTestCase { 14 | 15 | func testValidateAllClaims() { 16 | let jwts = ["all_claim_valid_1","all_claim_valid_2"].map(ReadSampleWithName) 17 | let validatorBase = RegisteredClaimValidator.issuer & RegisteredClaimValidator.subject & RegisteredClaimValidator.jwtIdentifier & RegisteredClaimValidator.audience & RegisteredClaimValidator.expiration & RegisteredClaimValidator.notBefore & RegisteredClaimValidator.issuedAt 18 | 19 | jwts.forEach { 20 | let validation = validatorBase.validateToken($0) 21 | XCTAssertTrue(validation.isValid, "\(validation)") 22 | } 23 | 24 | let validatorValues = RegisteredClaimValidator.issuer.withValidator {$0 == "kreactive"} & 25 | RegisteredClaimValidator.subject.withValidator {$0 == "antoine"} & 26 | RegisteredClaimValidator.jwtIdentifier.withValidator{$0 == "123456789"} & 27 | RegisteredClaimValidator.audience.withValidator {$0.contains("test-app")} 28 | 29 | jwts.forEach { 30 | let validation = validatorValues.validateToken($0) 31 | XCTAssertTrue(validation.isValid, "\(validation)") 32 | } 33 | } 34 | func testValidateAllClaimsSigned() { 35 | let validator = RegisteredClaimValidator.issuer.withValidator {$0 == "kreactive"} & 36 | RegisteredClaimValidator.subject.withValidator {$0 == "antoine"} & 37 | RegisteredClaimValidator.jwtIdentifier.withValidator{$0 == "123456789"} & 38 | RegisteredClaimValidator.audience.withValidator {$0.contains("test-app")} & 39 | HMACSignature(secret: "secret".data(using: String.Encoding.utf8)!, hashFunction: .sha256) 40 | 41 | let jwt = ReadSampleWithName("all_claim_valid_2_signed") 42 | let validation = validator.validateToken(jwt) 43 | XCTAssertTrue(validation.isValid, "\(validation)") 44 | } 45 | 46 | func testValidateClaimsGetter() { 47 | let jwts = ["all_claim_valid_1","all_claim_valid_2"].map(ReadSampleWithName) 48 | jwts.forEach { 49 | XCTAssertTrue($0.payload.audience.contains("test-app")) 50 | XCTAssertTrue($0.payload.issuer! == "kreactive") 51 | XCTAssertTrue($0.payload.subject! == "antoine") 52 | XCTAssertTrue($0.payload.jwtIdentifier! == "123456789") 53 | XCTAssertTrue($0.payload.expiration!.timeIntervalSinceNow >= 0) 54 | XCTAssertTrue($0.payload.notBefore!.timeIntervalSinceNow <= 0) 55 | XCTAssertTrue($0.payload.issuedAt != nil) 56 | } 57 | } 58 | func testValidateClaimsEmpty() { 59 | let tokens = ["empty","empty2"].map(ReadSampleWithName) 60 | tokens.forEach { jwt in 61 | XCTAssertTrue(jwt.payload.audience == []) 62 | XCTAssertNil(jwt.payload.issuer) 63 | XCTAssertNil(jwt.payload.subject) 64 | XCTAssertNil(jwt.payload.jwtIdentifier) 65 | XCTAssertNil(jwt.payload.expiration) 66 | XCTAssertNil(jwt.payload.notBefore) 67 | XCTAssertNil(jwt.payload.issuedAt) 68 | 69 | let validator = RegisteredClaimValidator.issuer & RegisteredClaimValidator.subject & RegisteredClaimValidator.jwtIdentifier & RegisteredClaimValidator.audience & RegisteredClaimValidator.expiration & RegisteredClaimValidator.notBefore & RegisteredClaimValidator.issuedAt 70 | let validation = validator.validateToken(jwt) 71 | XCTAssertFalse(validation.isValid) 72 | 73 | let validatorOptional = RegisteredClaimValidator.issuer.optional & RegisteredClaimValidator.subject.optional & RegisteredClaimValidator.jwtIdentifier.optional & RegisteredClaimValidator.audience.optional & RegisteredClaimValidator.expiration.optional & RegisteredClaimValidator.notBefore.optional & RegisteredClaimValidator.issuedAt.optional 74 | let validationOpt = validatorOptional.validateToken(jwt) 75 | XCTAssertTrue(validationOpt.isValid) 76 | } 77 | 78 | } 79 | func testOrCombine() { 80 | let jwt = ReadSampleWithName("RS512") 81 | let verifier = RSAPKCS1Verifier(key : SamplePublicKey, hashFunction: .sha512) 82 | let otherVerifier = HMACSignature(secret: "secret".data(using: String.Encoding.utf8)!, hashFunction: .sha512) 83 | XCTAssertTrue((verifier|otherVerifier).validateToken(jwt).isValid) 84 | XCTAssertTrue((otherVerifier|verifier).validateToken(jwt).isValid) 85 | 86 | } 87 | func testInvalidAudience() { 88 | let invalidFormat = ReadSampleWithName("invalid_aud_format") 89 | XCTAssertTrue(invalidFormat.payload.audience == []) 90 | let validationFormat = RegisteredClaimValidator.audience.optional.validateToken(invalidFormat) 91 | XCTAssertFalse(validationFormat.isValid) 92 | 93 | } 94 | func testInvalidExp() { 95 | let invalidFormat = ReadSampleWithName("invalid_exp_format") 96 | XCTAssertNil(invalidFormat.payload.expiration) 97 | let validationFormat = RegisteredClaimValidator.expiration.optional.validateToken(invalidFormat) 98 | XCTAssertFalse(validationFormat.isValid) 99 | 100 | let expired = ReadSampleWithName("invalid_expired") 101 | XCTAssertNotNil(expired.payload.expiration) 102 | let validationExpired = RegisteredClaimValidator.expiration.optional.validateToken(expired) 103 | XCTAssertFalse(validationExpired.isValid) 104 | } 105 | func testInvalidIat() { 106 | let invalidFormat = ReadSampleWithName("invalid_iat_format") 107 | XCTAssertNil(invalidFormat.payload.issuedAt) 108 | let validationFormat = RegisteredClaimValidator.issuedAt.optional.validateToken(invalidFormat) 109 | XCTAssertFalse(validationFormat.isValid) 110 | } 111 | func testInvalidIss() { 112 | let invalidFormat = ReadSampleWithName("invalid_iss_format") 113 | XCTAssertNil(invalidFormat.payload.issuer) 114 | let validationFormat = RegisteredClaimValidator.issuer.optional.validateToken(invalidFormat) 115 | XCTAssertFalse(validationFormat.isValid) 116 | } 117 | func testInvalidJWTIdentifier() { 118 | let invalidFormat = ReadSampleWithName("invalid_jti_format") 119 | XCTAssertNil(invalidFormat.payload.jwtIdentifier) 120 | let validationFormat = RegisteredClaimValidator.jwtIdentifier.optional.validateToken(invalidFormat) 121 | XCTAssertFalse(validationFormat.isValid) 122 | } 123 | func testInvalidNbf() { 124 | let invalidFormat = ReadSampleWithName("invalid_nbf_format") 125 | XCTAssertNil(invalidFormat.payload.notBefore) 126 | let validationFormat = RegisteredClaimValidator.notBefore.optional.validateToken(invalidFormat) 127 | XCTAssertFalse(validationFormat.isValid) 128 | 129 | let expired = ReadSampleWithName("invalid_nbf_immature") 130 | XCTAssertNotNil(expired.payload.notBefore) 131 | let validationExpired = RegisteredClaimValidator.notBefore.optional.validateToken(expired) 132 | XCTAssertFalse(validationExpired.isValid) 133 | } 134 | func testInvalidSub() { 135 | let invalidFormat = ReadSampleWithName("invalid_sub_format") 136 | XCTAssertNil(invalidFormat.payload.subject) 137 | let validationFormat = RegisteredClaimValidator.subject.optional.validateToken(invalidFormat) 138 | XCTAssertFalse(validationFormat.isValid) 139 | 140 | } 141 | 142 | func customClaimTest() { 143 | let _ = ClaimValidator(key: "customClaim", transform: { (jsonValue : Any) throws -> Int in 144 | guard let numberValue = jsonValue as? NSNumber else { 145 | throw ClaimValidatorError(message: "customClaim value \(jsonValue) is not the expected Number type") 146 | } 147 | return numberValue.intValue 148 | }).withValidator { 1..<4 ~= $0 } 149 | } 150 | } 151 | 152 | -------------------------------------------------------------------------------- /JSONWebTokenTests/DecodeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DecodeTests.swift 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 24/11/15. 6 | // 7 | 8 | import Foundation 9 | 10 | import JSONWebToken 11 | import XCTest 12 | 13 | class DecodeTests : XCTestCase { 14 | func testInvalidStructure() { 15 | let rawJWT = ["invalid_structure","invalid_structure_2"].map(ReadRawSampleWithName) 16 | rawJWT.forEach { 17 | do { 18 | let _ = try JSONWebToken(string : $0) 19 | XCTFail("should fail") 20 | } catch JSONWebToken.Error.badTokenStructure { 21 | 22 | } catch { 23 | XCTFail("should be a BadTokenStructure error \(error)") 24 | } 25 | } 26 | } 27 | func testInvalidBase64() { 28 | 29 | let invalidHeaderRawJWT = ReadRawSampleWithName("invalid_header_base64") 30 | do { 31 | let _ = try JSONWebToken(string : invalidHeaderRawJWT) 32 | XCTFail("should fail") 33 | } catch JSONWebToken.Error.cannotDecodeBase64Part(.header,_) { 34 | 35 | } catch { 36 | XCTFail("should be a .CannotDecodeBase64Part(.Header) error \(error)") 37 | } 38 | 39 | let invalidPayloadRawJWT = ReadRawSampleWithName("invalid_payload_base64") 40 | do { 41 | let _ = try JSONWebToken(string : invalidPayloadRawJWT) 42 | XCTFail("should fail") 43 | } catch JSONWebToken.Error.cannotDecodeBase64Part(.payload,_) { 44 | 45 | } catch { 46 | XCTFail("should be a .CannotDecodeBase64Part(.Payload) error \(error)") 47 | } 48 | 49 | let invalidSignatureRawJWT = ReadRawSampleWithName("invalid_signature_base64") 50 | do { 51 | let _ = try JSONWebToken(string : invalidSignatureRawJWT) 52 | XCTFail("should fail") 53 | } catch JSONWebToken.Error.cannotDecodeBase64Part(.signature,_) { 54 | 55 | } catch { 56 | XCTFail("should be a .CannotDecodeBase64Part(.Signature) error \(error)") 57 | } 58 | 59 | } 60 | func testInvalidJSON() { 61 | 62 | let invalidHeaderRawJWT = ReadRawSampleWithName("invalid_header_json") 63 | do { 64 | let _ = try JSONWebToken(string : invalidHeaderRawJWT) 65 | XCTFail("should fail") 66 | } catch JSONWebToken.Error.invalidJSON(.header,_) { 67 | 68 | } catch { 69 | XCTFail("should be a .InvalidJSON(.Header) error \(error)") 70 | } 71 | 72 | let invalidPayloadRawJWT = ReadRawSampleWithName("invalid_payload_json") 73 | do { 74 | let _ = try JSONWebToken(string : invalidPayloadRawJWT) 75 | XCTFail("should fail") 76 | } catch JSONWebToken.Error.invalidJSON(.payload,_) { 77 | 78 | } catch { 79 | XCTFail("should be a .InvalidJSON(.Payload) error \(error)") 80 | } 81 | 82 | } 83 | func testInvalidJSONStructure() { 84 | 85 | let invalidHeaderRawJWT = ReadRawSampleWithName("invalid_header_json_structure") 86 | do { 87 | let _ = try JSONWebToken(string : invalidHeaderRawJWT) 88 | XCTFail("should fail") 89 | } catch JSONWebToken.Error.invalidJSONStructure(.header) { 90 | 91 | } catch { 92 | XCTFail("should be a .InvalidJSON(.Header) error \(error)") 93 | } 94 | 95 | let invalidPayloadRawJWT = ReadRawSampleWithName("invalid_payload_json_structure") 96 | do { 97 | let _ = try JSONWebToken(string : invalidPayloadRawJWT) 98 | XCTFail("should fail") 99 | } catch JSONWebToken.Error.invalidJSONStructure(.payload) { 100 | 101 | } catch { 102 | XCTFail("should be a .InvalidJSON(.Payload) error \(error)") 103 | } 104 | } 105 | func testHeaderContent() { 106 | let missingAlgRawJWT = ReadRawSampleWithName("invalid_missing_alg") 107 | do { 108 | let _ = try JSONWebToken(string : missingAlgRawJWT) 109 | XCTFail("should fail") 110 | } catch JSONWebToken.Error.missingSignatureAlgorithm { 111 | 112 | } catch { 113 | XCTFail("should be a .MissingSignatureAlgorithm error \(error)") 114 | } 115 | 116 | let invalidAlgRawJWT = ReadRawSampleWithName("invalid_alg") 117 | do { 118 | let _ = try JSONWebToken(string : invalidAlgRawJWT) 119 | XCTFail("should fail") 120 | } catch JSONWebToken.Error.invalidSignatureAlgorithm("RS9000") { 121 | 122 | } catch { 123 | XCTFail("should be a .InvalidSignatureAlgorithm error \(error)") 124 | } 125 | 126 | let missingTyp = ReadRawSampleWithName("valid_missing_typ") 127 | do { 128 | let _ = try JSONWebToken(string : missingTyp) 129 | } catch { 130 | XCTFail("should not fail \(error)") 131 | } 132 | 133 | let invalidTyp = ReadRawSampleWithName("invalid_typ") 134 | do { 135 | let _ = try JSONWebToken(string : invalidTyp) 136 | XCTFail("should fail") 137 | } catch JSONWebToken.Error.typeIsNotAJSONWebToken { 138 | 139 | } catch { 140 | XCTFail("should be a .TypeIsNotAJSONWebToken error \(error)") 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /JSONWebTokenTests/ECDSATests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ECDSATests.swift 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 25/11/15. 6 | // 7 | 8 | import Foundation 9 | 10 | import JSONWebToken 11 | import XCTest 12 | 13 | class ECDSATests : XCTestCase { 14 | 15 | func testES256Decode() { 16 | let _ = ReadSampleWithName("ES256") 17 | } 18 | func testES384Decode() { 19 | let _ = ReadSampleWithName("ES384") 20 | } 21 | func testES512Decode() { 22 | let _ = ReadSampleWithName("ES512") 23 | } 24 | } -------------------------------------------------------------------------------- /JSONWebTokenTests/GenerateTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenerateTests.swift 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 25/11/15. 6 | // Copyright © 2015 Antoine Palazzolo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | import JSONWebToken 13 | import XCTest 14 | 15 | class GenerateTests : XCTestCase { 16 | 17 | func testGenerateWithNone() { 18 | do { 19 | let jwt = try JSONWebToken(payload : SamplePayload) 20 | XCTAssert(jwt.decodedDataForPart(.header) != Data()) 21 | XCTAssert(jwt.decodedDataForPart(.payload) != Data()) 22 | XCTAssert(jwt.decodedDataForPart(.signature) == Data()) 23 | 24 | } catch { 25 | XCTFail("should not fail \(error)") 26 | } 27 | } 28 | func testGenerateAllClaims() { 29 | do { 30 | var payload = JSONWebToken.Payload() 31 | 32 | payload.issuer = "kreactive" 33 | XCTAssertNotNil(payload.issuer) 34 | XCTAssert(payload.issuer == "kreactive") 35 | payload.issuer = nil 36 | XCTAssertNil(payload.issuer) 37 | payload.issuer = "kreactive" 38 | 39 | payload.subject = "antoine" 40 | XCTAssertNotNil(payload.subject) 41 | XCTAssert(payload.subject == "antoine") 42 | payload.subject = nil 43 | XCTAssertNil(payload.subject) 44 | payload.subject = "antoine" 45 | 46 | payload.audience = ["coucou"] 47 | XCTAssert(payload.audience.count == 1) 48 | XCTAssert(payload.audience == ["coucou"]) 49 | payload.audience = [] 50 | XCTAssert(payload.audience == []) 51 | payload.audience = ["coucou","coucou2"] 52 | XCTAssert(payload.audience == ["coucou","coucou2"]) 53 | 54 | 55 | let expirationDate = Date.distantFuture 56 | 57 | payload.expiration = expirationDate 58 | XCTAssertNotNil(payload.expiration) 59 | XCTAssertEqual(payload.expiration!.timeIntervalSince1970, expirationDate.timeIntervalSince1970, accuracy: 0.9999999) 60 | payload.expiration = nil 61 | XCTAssertNil(payload.expiration) 62 | payload.expiration = expirationDate 63 | 64 | let notBeforeDate = Date.distantPast 65 | 66 | payload.notBefore = notBeforeDate 67 | XCTAssertNotNil(payload.notBefore) 68 | XCTAssertEqual(payload.notBefore!.timeIntervalSince1970, notBeforeDate.timeIntervalSince1970, accuracy: 0.9999999) 69 | payload.notBefore = nil 70 | XCTAssertNil(payload.notBefore) 71 | payload.notBefore = notBeforeDate 72 | 73 | let issuedAtDate = Date() 74 | payload.issuedAt = issuedAtDate 75 | XCTAssertNotNil(payload.issuedAt) 76 | XCTAssertEqual(payload.issuedAt!.timeIntervalSince1970, issuedAtDate.timeIntervalSince1970, accuracy: 0.9999999) 77 | payload.issuedAt = nil 78 | XCTAssertNil(payload.issuedAt) 79 | payload.issuedAt = issuedAtDate 80 | 81 | 82 | let jwti = UUID().uuidString 83 | payload.jwtIdentifier = jwti 84 | XCTAssert(payload.jwtIdentifier == jwti) 85 | payload.jwtIdentifier = nil 86 | XCTAssertNil(payload.jwtIdentifier) 87 | payload.jwtIdentifier = jwti 88 | 89 | let jwt = try JSONWebToken(payload : payload) 90 | if let stringData = String(data : jwt.rawData , encoding : String.Encoding.utf8) { 91 | let _ = try JSONWebToken(string : stringData) 92 | } else { 93 | XCTFail() 94 | } 95 | 96 | } catch { 97 | XCTFail("should not fail \(error)") 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /JSONWebTokenTests/HMACTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HMACTests.swift 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 23/11/15. 6 | // 7 | 8 | import XCTest 9 | import JSONWebToken 10 | import Security 11 | 12 | class HMACTests : XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | func testHS256VerifySuccess() { 24 | let jwt = ReadSampleWithName("HS256") 25 | let verifier = HMACSignature(secret: "secret".data(using: String.Encoding.utf8)!, hashFunction: .sha256) 26 | let result = verifier.validateToken(jwt) 27 | XCTAssertTrue(result.isValid) 28 | } 29 | func testHS256VerifyFailure() { 30 | let jwt = ReadSampleWithName("HS256") 31 | let verifier = HMACSignature(secret: "secretr".data(using: String.Encoding.utf8)!, hashFunction: .sha256) 32 | let result = verifier.validateToken(jwt) 33 | XCTAssertFalse(result.isValid) 34 | } 35 | func testHS384VerifySuccess() { 36 | let jwt = ReadSampleWithName("HS384") 37 | let verifier = HMACSignature(secret: "secret".data(using: String.Encoding.utf8)!, hashFunction: .sha384) 38 | let result = verifier.validateToken(jwt) 39 | XCTAssertTrue(result.isValid) 40 | } 41 | func testHS384VerifyFailure() { 42 | let jwt = ReadSampleWithName("HS384") 43 | let verifier = HMACSignature(secret: "secretr".data(using: String.Encoding.utf8)!, hashFunction: .sha384) 44 | let result = verifier.validateToken(jwt) 45 | XCTAssertFalse(result.isValid) 46 | } 47 | func testHS512VerifySuccess() { 48 | let jwt = ReadSampleWithName("HS512") 49 | let verifier = HMACSignature(secret: "secret".data(using: String.Encoding.utf8)!, hashFunction: .sha512) 50 | let result = verifier.validateToken(jwt) 51 | XCTAssertTrue(result.isValid) 52 | } 53 | func testHS512VerifyFailure() { 54 | let jwt = ReadSampleWithName("HS512") 55 | let verifier = HMACSignature(secret: "secretr".data(using: String.Encoding.utf8)!, hashFunction: .sha512) 56 | let result = verifier.validateToken(jwt) 57 | XCTAssertFalse(result.isValid) 58 | } 59 | 60 | func testHS256Sign() { 61 | let signer = HMACSignature(secret: "secret".data(using: String.Encoding.utf8)!, hashFunction: .sha256) 62 | do { 63 | let jwt = try JSONWebToken(payload : SamplePayload, signer : signer) 64 | let verifier = signer 65 | let result = verifier.validateToken(jwt) 66 | XCTAssertTrue(result.isValid) 67 | } catch { 68 | XCTFail("sign failed \(error)") 69 | } 70 | 71 | } 72 | func testHS384Sign() { 73 | let signer = HMACSignature(secret: "secret".data(using: String.Encoding.utf8)!, hashFunction: .sha384) 74 | do { 75 | let jwt = try JSONWebToken(payload : SamplePayload, signer : signer) 76 | let verifier = signer 77 | let result = verifier.validateToken(jwt) 78 | XCTAssertTrue(result.isValid) 79 | } catch { 80 | XCTFail("sign failed \(error)") 81 | } 82 | } 83 | func testHS512Sign() { 84 | let signer = HMACSignature(secret: "secret".data(using: String.Encoding.utf8)!, hashFunction: .sha512) 85 | do { 86 | let jwt = try JSONWebToken(payload : SamplePayload, signer : signer) 87 | let verifier = signer 88 | let result = verifier.validateToken(jwt) 89 | XCTAssertTrue(result.isValid) 90 | } catch { 91 | XCTFail("sign failed \(error)") 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /JSONWebTokenTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /JSONWebTokenTests/KeyUtilsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyUtilsTests.swift 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 25/11/15. 6 | // Copyright © 2015 Antoine Palazzolo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | @testable import JSONWebToken 13 | import XCTest 14 | 15 | 16 | private let keyBase64 = "MIICCgKCAgEApmlQ3ER3KIBy8kQj6rGwYSb73qVw+H1C27QtZT05jahaEMhf9kqwdhduqk/KpdRi/ghy8r1fhee5W8yrEZbEreQBG4BCCM4T6do6Xl53gU0JNOznx7smDfZsgtCpjbnf0wuiY5sqWgWoB7IDkQwq/V/ekBPZ2J97m43tBTP6J9pMYmU/pQJGN9jxNNtum8W84d9mZWm19Kar3i3KmDi7bJSAwZGXS9MKTPfl76jHVjsZ94iQEZBTNjoYUVc/E5y3/vVroq3NfE6dh4j0dfRZhfz6HJQtqy/dHNMu14chTvQFzN+HuFRUa0swEEsNjQqFmqRkB+sMDfw1mbjP3fb46pcnWdXHQyJP0q3vePLwanvwI7u32UlyaXe9bWlb6nuzBlqfwGzm7oT021yHUtRmK3Gr5/nUWwJzjvzEOn5hvnUUU37cw9WBb+itd+r9y469tBW2vZFyIodNSzgQ5/GCPbtfRjPKZ+Lfev3G0kjBRDKhcSFc3oakqcWdBC9C1KLKFYwZMRuE3wu7sQMk4PkTg5xnnUn8m9462DljfkieNAZzBwdIbPCGtu/dhQhaJcz/Dq0FgIkwoLXYzJvzgPuZq8MqHA/eJnssELvWRLoWLncyQz1giUgZvU4v+0xcMuMqQA+TsnIAEhNG8T8hsrVqD3dQvkbaWsgCCQY0EkjHeZUCAwEAAQ==" 17 | 18 | private let SampleKeyData = Data(base64Encoded: keyBase64, options: [])! 19 | 20 | private let keyBase64_2 = "MIICCgKCAgEAz5wSeoffVD3coUZQYZrMGuZgtloPioEX6VUrUhjYkeKsR27vph4BQhM0Vj8t/Ej2JF4BkREV2eHnWisJoL+ZfS4LxSz0Rkc4eKhJiol12XVOtE2xxrVDcmF4HMIs+k5Nl4vyIfhGqAlrKxPDJD2hZ3ROAscMfLwmdZU7IwgZpr02hbbVZ1DA3lzt6f6r9g/EMkFNl51Bxc9WIV7ymgQrFCeH/JJydiQ5wDS133m1aeNImksIA9O/bjifVc87+FPiK2dU8I1DlRWKBw0+YG0tW9ShDuG/jVdDHtWqPamGVu12sMXugcw3vVk6xrd5cdFjKd/ToVzfBpjXftyRdWwFduQQOv+05sLxaDOH+fF20PkBKgYWYmkAkzokZo6E5Ofy8Vvzf7UyYD+1AbFJ2rZkGh1LUM2OcaqH+LYMWVZBGESIkH0DOlwAk7B78yubIWV1295Y0tuxc47Fu+fMLIZQKx6D0MXpYwp5kK9ITBWYfVpXudFv5zjXWrcIM/2UdPhpxNVHS6vBg3c14tVXExARIKNux650f/P/rL6R/6O6R8Nzr2+u2VAnyCAO7BuVo006EId9sJe0OHEKjvJskXCfLMhscan/JOu0/r9C8TwVvaPI0LLETcIl/5ip9Ht4NWuOSQ1oQ8/cEZx7v2+SkLMYDD/FjDdvycXDtix/OM0Rg28CAwEAAQ==" 21 | 22 | private let SampleKeyData2 = Data(base64Encoded: keyBase64_2, options: [])! 23 | 24 | class KeyUtilsTests : XCTestCase { 25 | override func setUp() { 26 | super.setUp() 27 | self.cleanRegisteredKeys() 28 | } 29 | override func tearDown() { 30 | self.cleanRegisteredKeys() 31 | super.tearDown() 32 | } 33 | fileprivate func cleanRegisteredKeys() { 34 | RSAKey.removeKeyWithTag("testAddKey") 35 | RSAKey.removeKeyWithTag("testAddBadKey") 36 | RSAKey.removeKeyWithTag("PublicPEMKey") 37 | RSAKey.removeKeyWithTag("ModulusExponent") 38 | } 39 | 40 | func testAddUpdateRemoveKey() { 41 | self.cleanRegisteredKeys() 42 | XCTAssertNil(RSAKey.registeredKeyWithTag("testAddKey")) 43 | do { 44 | try RSAKey.registerOrUpdateKey(SampleKeyData, tag : "testAddKey") 45 | } catch { 46 | XCTFail("should not fail \(error)") 47 | } 48 | XCTAssertNotNil(RSAKey.registeredKeyWithTag("testAddKey")) 49 | let key1Data = try! getKeyData("testAddKey") 50 | 51 | do { 52 | try RSAKey.registerOrUpdateKey(SampleKeyData2, tag : "testAddKey") 53 | } catch { 54 | XCTFail("should not fail \(error)") 55 | } 56 | XCTAssertNotNil(RSAKey.registeredKeyWithTag("testAddKey")) 57 | let key2Data = try! getKeyData("testAddKey") 58 | XCTAssertNotEqual(key1Data, key2Data) 59 | 60 | RSAKey.removeKeyWithTag("testAddKey") 61 | XCTAssertNil(RSAKey.registeredKeyWithTag("testAddKey")) 62 | } 63 | 64 | func testAddBadKeyFormat() { 65 | self.cleanRegisteredKeys() 66 | XCTAssertNil(RSAKey.registeredKeyWithTag("testAddBadKey")) 67 | do { 68 | try RSAKey.registerOrUpdateKey("this_is_not_a_rsa_key".data(using: String.Encoding.utf8)!, tag : "testAddBadKey") 69 | XCTFail("should fail") 70 | } catch RSAKey.KeyUtilError.badKeyFormat {} 71 | catch { 72 | XCTFail("should be a KeyUtilError.BadKeyFormat : \(error)") 73 | } 74 | XCTAssertNil(RSAKey.registeredKeyWithTag("testAddKey")) 75 | 76 | } 77 | func testAddPublicPEMKey() { 78 | self.cleanRegisteredKeys() 79 | let pemPath = Bundle(for: type(of: self)).path(forResource: "public", ofType: "pem")! 80 | let pemData = try! Data(contentsOf: URL(fileURLWithPath: pemPath)) 81 | 82 | XCTAssertNil(RSAKey.registeredKeyWithTag("PublicPEMKey")) 83 | do { 84 | try RSAKey.registerOrUpdatePublicPEMKey(pemData, tag : "PublicPEMKey") 85 | } 86 | catch { 87 | XCTFail("should not fail : \(error)") 88 | } 89 | XCTAssertNotNil(RSAKey.registeredKeyWithTag("PublicPEMKey")) 90 | RSAKey.removeKeyWithTag("PublicPEMKey") 91 | XCTAssertNil(RSAKey.registeredKeyWithTag("PublicPEMKey")) 92 | 93 | } 94 | func testModulusExponent() { 95 | self.cleanRegisteredKeys() 96 | let modulusPath = Bundle(for: type(of: self)).path(forResource: "public", ofType: "modulus")! 97 | let modulusData = try! Data(contentsOf: URL(fileURLWithPath: modulusPath)) 98 | let exponentPath = Bundle(for: type(of: self)).path(forResource: "public", ofType: "exponent")! 99 | let exponentData = try! Data(contentsOf: URL(fileURLWithPath: exponentPath)) 100 | XCTAssertNil(RSAKey.registeredKeyWithTag("ModulusExponent")) 101 | do { 102 | try RSAKey.registerOrUpdateKey(modulus : modulusData, exponent : exponentData, tag : "ModulusExponent") 103 | } 104 | catch { 105 | XCTFail("should not fail : \(error)") 106 | } 107 | XCTAssertNotNil(RSAKey.registeredKeyWithTag("ModulusExponent")) 108 | RSAKey.removeKeyWithTag("ModulusExponent") 109 | XCTAssertNil(RSAKey.registeredKeyWithTag("ModulusExponent")) 110 | 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /JSONWebTokenTests/RSASSA_PKCS1Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONWebTokenTests.swift 3 | // JSONWebTokenTests 4 | // 5 | // Created by Antoine Palazzolo on 17/11/15. 6 | // Copyright © 2015 Antoine Palazzolo. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import JSONWebToken 11 | import Security 12 | 13 | class RSASSA_PKCS1Tests: XCTestCase { 14 | 15 | override func setUp() { 16 | super.setUp() 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | } 19 | 20 | override func tearDown() { 21 | // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | super.tearDown() 23 | } 24 | 25 | func testRS256VerifySuccess() { 26 | let jwt = ReadSampleWithName("RS256") 27 | let verifier = RSAPKCS1Verifier(key : SamplePublicKey, hashFunction: .sha256) 28 | XCTAssertTrue(verifier.validateToken(jwt).isValid) 29 | } 30 | func testRS384VerifySuccess() { 31 | let jwt = ReadSampleWithName("RS384") 32 | let verifier = RSAPKCS1Verifier( key : SamplePublicKey,hashFunction: .sha384) 33 | XCTAssertTrue(verifier.validateToken(jwt).isValid) 34 | } 35 | func testRS512VerifySuccess() { 36 | let jwt = ReadSampleWithName("RS512") 37 | let verifier = RSAPKCS1Verifier( key : SamplePublicKey,hashFunction: .sha512) 38 | XCTAssertTrue(verifier.validateToken(jwt).isValid) 39 | } 40 | 41 | func testRS256VerifyFailure() { 42 | let jwt = ReadSampleWithName("RS256_2") 43 | let verifier = RSAPKCS1Verifier(key : SamplePublicKey , hashFunction: .sha256) 44 | XCTAssertFalse(verifier.validateToken(jwt).isValid) 45 | } 46 | func testRS384VerifyFailure() { 47 | let jwt = ReadSampleWithName("RS384_2") 48 | let verifier = RSAPKCS1Verifier(key : SamplePublicKey, hashFunction: .sha384) 49 | XCTAssertFalse(verifier.validateToken(jwt).isValid) 50 | } 51 | func testRS512VerifyFailure() { 52 | let jwt = ReadSampleWithName("RS512_2") 53 | let verifier = RSAPKCS1Verifier(key : SamplePublicKey, hashFunction: .sha512) 54 | XCTAssertFalse(verifier.validateToken(jwt).isValid) 55 | } 56 | func testVerifyOtherAlg() { 57 | let jwt = ReadSampleWithName("HS256") 58 | let verifier = RSAPKCS1Verifier(key : SamplePublicKey, hashFunction: .sha512) 59 | XCTAssertFalse(verifier.validateToken(jwt).isValid) 60 | } 61 | func testRS256Sign() { 62 | let signer = RSAPKCS1Signer(hashFunction: .sha256, key: SamplePrivateKey) 63 | do { 64 | let jwt = try JSONWebToken(payload : SamplePayload, signer : signer) 65 | let verifier = RSAPKCS1Verifier(key : SamplePublicKey, hashFunction: .sha256) 66 | XCTAssertTrue(verifier.validateToken(jwt).isValid) 67 | } catch { 68 | XCTFail("should not fail \(error)") 69 | } 70 | } 71 | func testRS384Sign() { 72 | let signer = RSAPKCS1Signer(hashFunction: .sha384, key: SamplePrivateKey) 73 | do { 74 | let jwt = try JSONWebToken(payload : SamplePayload, signer : signer) 75 | let verifier = RSAPKCS1Verifier(key : SamplePublicKey, hashFunction: .sha384) 76 | XCTAssertTrue(verifier.validateToken(jwt).isValid) 77 | } catch { 78 | XCTFail("should not fail \(error)") 79 | } 80 | } 81 | func testRS512Sign() { 82 | let signer = RSAPKCS1Signer(hashFunction: .sha512, key: SamplePrivateKey) 83 | do { 84 | let jwt = try JSONWebToken(payload : SamplePayload, signer : signer) 85 | let verifier = RSAPKCS1Verifier(key : SamplePublicKey, hashFunction: .sha512) 86 | XCTAssertTrue(verifier.validateToken(jwt).isValid) 87 | } catch { 88 | XCTFail("should not fail \(error)") 89 | } 90 | } 91 | func testCertificateImport() { 92 | let certificateData = try! Data(contentsOf: URL(fileURLWithPath: Bundle(for : type(of: self)).path(forResource: "TestCertificate", ofType: "cer")!)) 93 | do { 94 | let _ = try RSAKey(certificateData : certificateData) 95 | } catch { 96 | XCTFail("should not fail \(error)") 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /JSONWebTokenTests/RSASSA_PSSTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RSASSA_PSSTests.swift 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 25/11/15. 6 | // 7 | 8 | import Foundation 9 | import JSONWebToken 10 | import XCTest 11 | 12 | class RSASSA_PSSTests : XCTestCase { 13 | 14 | func testPS256Decode() { 15 | let _ = ReadSampleWithName("PS256") 16 | } 17 | func testPS384Decode() { 18 | let _ = ReadSampleWithName("PS384") 19 | } 20 | func testPS512Decode() { 21 | let _ = ReadSampleWithName("PS512") 22 | } 23 | } -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/EC_privateKey: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIIuzCr6MStFTpAe8ED+OsHdQPWWktZ7w8NJ7e2IglbDvoAoGCCqGSM49 3 | AwEHoUQDQgAEFFdoENqtslRnF1TSKmR4By48NFeTTlU0Ij2hHPztWJ95kGKUzkSq 4 | 3dfSfg1L5iycW6wDvE3Me/2vFiaGsqDbrQ== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/EC_publicKey.pub: -------------------------------------------------------------------------------- 1 | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBRXaBDarbJUZxdU0ipkeAcuPDRXk05VNCI9oRz87VifeZBilM5Eqt3X0n4NS+YsnFusA7xNzHv9rxYmhrKg260= antoine@Antoines-MacBook-Pro.local 2 | -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/ES256.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.XPNX_sPapRMSyhrm2VVX1j9mrMkJGJLYWtZHt1-bNFep2Ipur33dH79ZCgTopRJZXJnAbXSP_6KhHBjMkfOQmw -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/ES384.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.vzHmE1TOAXZx-Os3b4TFiNEFM2gCZkTB_ddKG7RbhSTb30Cgd-twdx85DrvhVYyqkZcZfOdYObG3PIKbf6Sfpg -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/ES512.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.4hW3nSfJw9BqkLnGiXS1USu18pYaqfWS0v9kCWillP6ya587CFWr012w_FOqOqepN88tlJG_nRTkIrPL8WVuaQ -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/GenerateSample.py: -------------------------------------------------------------------------------- 1 | #https://github.com/jpadilla/pyjwt 2 | #pip install PyJWT 3 | 4 | import jwt 5 | import sys 6 | import os.path 7 | 8 | sampleDirectory = os.path.split(sys.argv[0])[0] 9 | 10 | def writeToSampleDirectory(content,name): 11 | f = open(sampleDirectory+'/'+name, 'w') 12 | f.write(content) 13 | f.close() 14 | 15 | cryptoPayload = { 'sub': '1234567890','name': 'John Doe' } 16 | cryptoSymSecret = 'secret' 17 | 18 | 19 | publicKeyFile = open(sampleDirectory+'/public.pem', 'r') 20 | cryptoPublicKey = publicKeyFile.read() 21 | publicKeyFile.close() 22 | 23 | privateKeyFile = open(sampleDirectory+'/private.pem', 'r') 24 | cryptoPrivateKey = privateKeyFile.read() 25 | privateKeyFile.close() 26 | 27 | publicKeyFile2 = open(sampleDirectory+'/public2.pem', 'r') 28 | cryptoPublicKey2 = publicKeyFile2.read() 29 | publicKeyFile2.close() 30 | 31 | privateKeyFile2 = open(sampleDirectory+'/private2.pem', 'r') 32 | cryptoPrivateKey2 = privateKeyFile2.read() 33 | privateKeyFile2.close() 34 | 35 | ECpublicKeyFile = open(sampleDirectory+'/EC_publicKey.pub', 'r') 36 | ECcryptoPublicKey = ECpublicKeyFile.read() 37 | ECpublicKeyFile.close() 38 | 39 | ECprivateKeyFile = open(sampleDirectory+'/EC_privateKey', 'r') 40 | ECcryptoPrivateKey = ECprivateKeyFile.read() 41 | ECprivateKeyFile.close() 42 | 43 | writeToSampleDirectory(jwt.encode(cryptoPayload, 'secret', algorithm='HS256'),'HS256.jwt') 44 | writeToSampleDirectory(jwt.encode(cryptoPayload, 'secret', algorithm='HS384'),'HS384.jwt') 45 | writeToSampleDirectory(jwt.encode(cryptoPayload, 'secret', algorithm='HS512'),'HS512.jwt') 46 | 47 | writeToSampleDirectory(jwt.encode(cryptoPayload, cryptoPrivateKey, algorithm='RS256'),'RS256.jwt') 48 | writeToSampleDirectory(jwt.encode(cryptoPayload, cryptoPrivateKey, algorithm='RS384'),'RS384.jwt') 49 | writeToSampleDirectory(jwt.encode(cryptoPayload, cryptoPrivateKey, algorithm='RS512'),'RS512.jwt') 50 | 51 | writeToSampleDirectory(jwt.encode(cryptoPayload, cryptoPrivateKey, algorithm='PS256'),'PS256.jwt') 52 | writeToSampleDirectory(jwt.encode(cryptoPayload, cryptoPrivateKey, algorithm='PS384'),'PS384.jwt') 53 | writeToSampleDirectory(jwt.encode(cryptoPayload, cryptoPrivateKey, algorithm='PS512'),'PS512.jwt') 54 | 55 | writeToSampleDirectory(jwt.encode(cryptoPayload, ECcryptoPrivateKey, algorithm='ES256'),'ES256.jwt') 56 | writeToSampleDirectory(jwt.encode(cryptoPayload, ECcryptoPrivateKey, algorithm='ES384'),'ES384.jwt') 57 | writeToSampleDirectory(jwt.encode(cryptoPayload, ECcryptoPrivateKey, algorithm='ES512'),'ES512.jwt') 58 | 59 | writeToSampleDirectory(jwt.encode(cryptoPayload, cryptoPrivateKey2, algorithm='RS256'),'RS256_2.jwt') 60 | writeToSampleDirectory(jwt.encode(cryptoPayload, cryptoPrivateKey2, algorithm='RS384'),'RS384_2.jwt') 61 | writeToSampleDirectory(jwt.encode(cryptoPayload, cryptoPrivateKey2, algorithm='RS512'),'RS512_2.jwt') 62 | 63 | 64 | all_claim_valid_1 = { 65 | 'iss' : 'kreactive', 66 | 'sub' : 'antoine', 67 | 'aud' : 'test-app', 68 | 'exp' : 1735689600, 69 | 'nbf' : 0, 70 | 'iat' : 1448371704, 71 | 'jti' : '123456789' 72 | } 73 | writeToSampleDirectory(jwt.encode(all_claim_valid_1, None,algorithm='none'),'all_claim_valid_1.jwt') 74 | 75 | all_claim_valid_2 = { 76 | 'iss' : 'kreactive', 77 | 'sub' : 'antoine', 78 | 'aud' : ['test-app','test-app2'], 79 | 'exp' : 1735689600, 80 | 'nbf' : 0, 81 | 'iat' : 1448371704, 82 | 'jti' : '123456789' 83 | } 84 | writeToSampleDirectory(jwt.encode(all_claim_valid_2, None,algorithm='none'),'all_claim_valid_2.jwt') 85 | 86 | 87 | all_claim_valid_2_signed = { 88 | 'iss' : 'kreactive', 89 | 'sub' : 'antoine', 90 | 'aud' : ['test-app','test-app2'], 91 | 'exp' : 1735689600, 92 | 'nbf' : 0, 93 | 'iat' : 1448371704, 94 | 'jti' : '123456789' 95 | } 96 | 97 | writeToSampleDirectory(jwt.encode(all_claim_valid_2_signed, cryptoSymSecret ,algorithm='HS256'),'all_claim_valid_2_signed.jwt') 98 | writeToSampleDirectory(jwt.encode({}, None ,algorithm='none'),'empty.jwt') 99 | writeToSampleDirectory(jwt.encode({'exp' : '11/11/2025'}, None ,algorithm='none'),'invalid_exp_format.jwt') 100 | writeToSampleDirectory(jwt.encode({'exp' : 1448465478}, None ,algorithm='none'),'invalid_expired.jwt') 101 | 102 | writeToSampleDirectory(jwt.encode({'nbf' : '11/11/2025'}, None ,algorithm='none'),'invalid_nbf_format.jwt') 103 | writeToSampleDirectory(jwt.encode({'nbf' : 1762819200}, None ,algorithm='none'),'invalid_nbf_immature.jwt') 104 | 105 | writeToSampleDirectory(jwt.encode({'iat' : '11/11/2025'}, None ,algorithm='none'),'invalid_iat_format.jwt') 106 | writeToSampleDirectory(jwt.encode({'iss' : 1762819200 }, None ,algorithm='none'),'invalid_iss_format.jwt') 107 | writeToSampleDirectory(jwt.encode({'sub' : 1762819200 }, None ,algorithm='none'),'invalid_sub_format.jwt') 108 | writeToSampleDirectory(jwt.encode({'aud' : 1762819200 }, None ,algorithm='none'),'invalid_aud_format.jwt') 109 | writeToSampleDirectory(jwt.encode({'jti' : 1762819200 }, None ,algorithm='none'),'invalid_jti_format.jwt') 110 | 111 | -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/HS256.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Q6CM1qIz2WTgTlhMzpFL8jI8xbu9FFfj5DY_bGVY98Y -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/HS384.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.k2zvPaQjdbW-uZ6OBreIqHI9HeckcAjzZCQV8awkZ9iuscJJjrkt_N3aoEkVwG-i -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/HS512.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.dV2yzTIOD98i-52Na3P-7kqfaI8cIkPOQ7zrDCY0cHBN049rvo-IZsia1NVsG_9EE_H9vwlGBmyzWIn4HATOzA -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/PS256.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.YtzPdGg48rssUieig1RXsis4CLNqYVI24DMJMgV6sxMkB2leTZD5OUT6aO7s1uB_F0giCl3S92s9gpJmTdyRdC_-ob2fCrgPKj2iI103zg4UR0Ki3I5zlJFD1NnPWc0msV8h1UoAJ-SrPiFFHbtQVJVg7MNRKPvle2iElH-J4GfrxDs309AX5F-1SY3k8kJdjXHdD_fLTEVS2YV2GFG9wh4h2WfUinGl9oUJ2_Wbb89Vve-IrrooqinuXj5pTmPBHnsmf7E1mbcnL_yfHbo6caa0Gl56J3jQI8HB3G8hDaDDV9b76l4oogTmj8UOATT6JSxugbpM-kyqUdrpFH88d8w8PYQbhAQK-6ssBL1x2lJs5Qy0AOV27tjPHjJKimkRQHo3Jq1tiVqXzOVT-jtvEEMzUJRFDsChvrxJatxNjBrG5X1V_1Dgbxte0D1FYPoBwNTOddjyLvaxTxKzuUty8MQ54Vx9ZPsODujhxwhLy1x3_PFEKNIcWPb4xkViMjyq4sIjjjblH0unCiqd1l_Mc2LhX6bp_5VzR_nX28eXFDN-eLCB2xCgCOVWdTRrnCUQhP94lya5k5qrsyIQyozoT5lxp3HhtxozaurId5CGl5nu0XNdMZGr8w3cT2PD1jsCdtQOumRQJhQ2QN7cIeOYx_xPwXADj_CqV3z9rSL8oxg -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/PS384.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.aXsRblocHjX4AAcSdHJBMl_mj1nOc11tGUd3p_lFYI8j00zzcnGC-8jmkTw0acgzYisG_WqyaH3NQeo8EyTU8Hw8ajs8LJTlfxGVHd2pEmV3cpOINGh8mj0mGq0h4Og2G0TvXzHSdwvx-JCzDMfrpwhLbQFqfOK-CJs8iWKWWTanSgPsBwBTk6gXStTuBtbCBDnXWfI4-EJb56t2j8JRdMwv9YmwKVUMReD_hDI5JmVdmEUjsKvCEldqGokCTvbPdaKs5NRic3c7ikoMUEHhZ21IKB-C7pD3jB9_sEdSyRhcDhaSD0kl_gmOjyC1TZ6rP2zr_WZ_Zji_VJYvmpGCUCfrIc9jHgEmps29ArPLpY5SO3cm-SLgQXHaukzA0N1n2LkdEUzRVQTll9Kc7DGg66mEHaF26rLWJD75liHkE4ek44LabxpsP8Ntgbgbbp27iUH0cgGMOINaJ5a6vWAJFa-QRZmooshdJ14QLUcbQr-XVc-4beOCOIHm7KV_VicVlOmH6NseYR5qNO9JN9iIbDUcmg3DGD1BbXHkJ37eFMEQ01mSOhV5zXVmnAR2mn5qYjazWrCvTyz88KhWhBf3XfeR7R4XJmvOD3Wl-MnFZxeReV5jZoUVY57bUrVukwUyHzbghLh6JAxvgdcML5yoQe2N1Rmq3HGIkhzdESn3qS0 -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/PS512.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Gtw2eu9zJFlMB15Q_32CBp9U_zdvqMKFOpYHC66NKdYH8KzcOYR8qB680-NLMESEz4Xv2r3L-Asbv0pWx0hdHISdryFBV5kNQlsvYlulUJ5HpbY5x6UmPa8-39WBgVaWA-XEOBSCf04MNKOuCtrx4ixHqNmdeA_iNLMQI6uq9MKGJPC0SVeeJn_XvVLvrHrWhI3HWtKjwDH9xEJj4S4oMg3dyuLGCUJufgRtdlINBS_Dta5iGtYdm_LqKOHwIfT9zURzU0ZFUb4UJ-ybgSdhdhvkg-Mvd6YI8EAypZjCaopku-Rlfi0ntrC9WCQ8wAsycjuDuKayAiiSlgULhwUX22TGoCWIPY_6EjgZP9xpHK_E2Eyyp5t450_e_wstB3rddXZpWRwvvoZyg0Sx3K-kgoguGgbi_jVTPCvv-qnO_FYMu-FjJY_jPFVRwcy6rKENezprOruWk5rD8fJmDXgp-dsN7lVWcB3K3_iIyJsaAGvN-_sgeu6Fz3mcR8LYF5gvFUNLJkuIXACi2XjdRCSlHw0TkQbY6pwlE0vcvEMSM6KJ4O-7v3DivS4p-lkWzy75Kp8tGxQj9lyAUnl3RGsoEfE3TS1ZWX8R06FjQDILjZT_rXouY7DKxC6i-uqb5Bdk4foqDMWTfh1WSuf3UYEJGj3jxchXByvFR-2NN3wko2w -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/RS256.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.UMHdGH5THP0G-or1Zwpl8nrIyAYSZNtiV4oYJbLCBA1wwusEXt7tpKGGmcy_NfJNNLiGLB_N7EYyJAcfc-DEEcHUbIOZiYEbun3C2R4WEtcGImBbH44XCMS9b_4EYoeVY-AH0dq6u0FE9UrFS-qj_QbvcaOGxcCUAlI5etimGK9yWvt8LMsJ0-BKSO0ho7HQwoprHQFj6glI2ogqjYZOdWKKhMiKGu-KTzaPsndXf6yJ15d8lGwL51pHHMLMxMuZPO5HsxL5oeagriRKGyGhstVsW1wKiVBO_PQtMqIPfDN9Od06K1-z0_7fXg83qMTP7pj7fx8eJy90zJ_3tVBfY7m0p9zES1OEGm2hbz_SLzU9TPUTplIykqn-Rj50nU79uZUE0D7w6f1oZCczPtFCq4yzany9TMrZLTOLksTlDskAm2yhLqPLSghdaiS5amGNJv4baFOHEQkjccWx_s1icj6LmPgc33l7OOxKVwA4xE9cnk2qSsHry5Av2em6TkrwLnlVUDEFeSSJ8W3AqhkAenwwSDix-EQ3mhEmdJ_4ZfvrwdC7oiHoyeRie2aqQjP96E7f734SiCqw4tPMEDz0xDQYH8kb_nSz7FE6v5k-j7mc_kqm4V4S0BvgDkRlWXrAth6VlkD8Tx2pxOyLI2kofbYtWS0l-BlmTQsz1OC0ie0 -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/RS256_2.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.tJqJJDJAGQ_tMhBndUjdxIxmQ_r4LCEotyDiMMK5qsg8rjhO8Dmb4gFmdZJZjfQZBq4c1_YAy3kVa_7eoxl4fdO3LkSiWCdBHa7nrU3aqXVm39CPZfqsNVf0CFdUloradZN7O1uf3V_OaBa6eEcr_PCl8S-bIqI2UYw23yLQ09-moElPQScPoVdiiKAiBzdt2iEEuDmw3EFvb-qQ20EdKDAXRMMC6kIommaHi1Lo36ckqUqgo1GUPy9nXJfluuXnuv_vKaaYx_fcpL8p8uIvtNVAJKtYjMiykYtUiYN7Wb_AbesqthFN00TQd9d7JSje-Wqp6i_1nyv6pxZspf3IExitqZ1yCELP8jYWw3dyN16CfMZxrCGNSuvlF2mkStnryTE1K7i2EU4CnTTuSrAM-b7gxNtlca91evKwiGW6rzCel3rNLG2RGlcdcok1i6x6UzvMeOmlmmKFncZF14rtv2tEBE_SCTolTMZNQErguZI60RFaJH9dhscyBRVP6_nxG4f-teYV8lFI2dkl8EYDA5lz13fhPoax0E4f8OG7848F0sR2Ov3m1E9K5gONA37L3iqN5TyjixcUAoWQsVsIMjAJOjbsU3Me7ry3fFplVPqEkwdNdOOYRf-AfDHCWuBMqmjyn04nPeQh5f2pJqr_9Vol8eITS8bW5ZFdZbmmBxQ -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/RS384.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.URhpZ3ECbAHSa5DtLUmIZWaK-N1Nhw1g5WviBKK67iQ11ACAr30j3TeeuGjlGaK21QXBgVCYZC3l_OK-X9qLu4Df-x3MuG62KBuzaqAyCkaCdNwN1Ht3UA04YMwmbN-oUC9orzzPv8_UPzv8IN__PGPUv2nR_MIBX6vVKzjbCiJtqJCkvTjW8fWOxafoJ9Y05-wxTW_R8zvNbNp-l3XND56ScyyT5cki9dM_zDQ9iyggRiWNSoWBilYo6wa1VWJYQiB_tCQiwYBRzTDTH4tA31tnyPPtRk9OTVcd81caGrnVVFl36uGgrV-cFglLiDVFOjDB2AHhEb3nCsPmBdCWMpv4x1BaJnik0suGyzZRw3T5QXHeLAX6xib4AZwt4a6DJGogXd8TZJB7FBlWwiE-9Qd7PrCtp5gFg2EU3uE5DpTJfF51Gfs7azwQq7mvDsTpb_aF6oBFbDP3--qMruSpyRq8cDckqPIMLR8cPoa9mWosjnJKXv5tkM74LflcI3AteE3CMUjdDEsLQ3P9xMsfUU4WL0RiNHtK5DjVHiThzBVkOP8T4P06vBa6U6hIcHWTZHAqttCWFx0j5PnDHWYqe3D7PB6V090iK7x-Bavd2QJVaxKcxy6TCNA92C9iCf1clk4Eb111Wv8XSUOmHoUVT_K3Dlmn6Xsz2jAXTD51uCo -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/RS384_2.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.SsPTtG8A-0dpVZoQww6YgEkMya99cjMSrImO-KalqcwnoXhA6VcpustDLdd11pq6ndYPlDCBRjK93eOoVcJ9Xdc_Ca2y3DcpbGKa9n5loXz591ZPdTo5HCKBDTeakGFxPMWjNBqA2yJQ3PUx-gKcF3MOvmXf4NgQYcNe2WQvKp0sJ2o-rIaz35-51m1At_m5smenRj7DahB6zwW5RVHCRDEKKAw3pbBM3_4X2dZKybGh05c82kFl93eAulB1netD_ChTH8EhZUVVigVi_a2zt1M6B_zCkADo2ua4Sm5w1d2JUDhkESDQTfbbVRalwiwNN3gAYioKFjiFx3VV-vjbXdGJeseiAwuTuhgR7_hXTY-6oV2AkZX-WfJBD4hrfRXqcovsm1NfTraZRjBRnqJUH0m_gCC1ieeil8IGLvcd6ZihbRQ4D9WuSgM5sr19ejLzWOtjFHIrneXtlfAd5BA-ohj4p_f-WhcL98JF5bDnH15EMX8ghNedNHq_JoePkEqSx5-iKsWtjE3qoj17dMdSN1SLxwtYF8jQiWoDnwB_-kYbeW2Hpkt7b95eREOQBl26Fyma3wgnllE6RHZjBQeDsD96e8G7i3s_o38QJf9NwRtmsU9kWsPmRPI03SkmXHs7SHsMxZqalSqnMP--X-B5JKScdXCASc4cK2y3EElHp7I -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/RS512.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.T1xY7vOgX54uE1pzaC824ODOzSyhfi4tdrlhPoAzc7sppC9Y2c3fw2a2ixyYVEyShWItXSapGHEgPIxdvlH-jfOBDo0iymjgTYYJWxyoLy-m4-9FxH643OsbbKJd1jkGUkk1VgJlksvYJksZnerAb8eDaQ-NkioMLU34P4-yTBQlkgHByq5MFoutI_TJ5ijcmzuugRRn6fCD6MoZh9uDjNpuug7X_MF4_WB_fnv-fW1wlmZxZFDQGzdQIfUfbXpfhyG7eeXP5LbPWE0NpninrwKzkAhPZ6XydGBJmdEG8JyCZKG8u3KbYd9wBMDTo2U7gESEz5EWHgYExuEhlAy7E8dqEXad2IOZfneGbYe1SdrRcltC_FH1B6hWfoGwwK6cBwNYHLCCSrAf1WLrTejpIe8efhWBJ3YW8PX3KBS_elvHFZ-YlCiKi4Z-YpJWpPFtrtcIKZrTktLMLxbuY2uMp0ljb9tpIOJDfYoV3q94hEIEbx_q4qWyBX2ho3XZq1RL5kS1EBlPZ3lHf9dHtUiGY6HPiFy_Xt-7z1Uf7vNvwerkXzKnG6cnIfRfBS6Az1T1avU_v2ySrwrvw7z2GjXaXA8tpDoOqM9A9rqvpjX88LBYwQt-becut94P-h0VE9vk562xWPhqIdtJ9zDiDeYxUAIOQsquhFH0Mdwd5IKORFg -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/RS512_2.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.fnrfyp-pAPZG3iBxyMlaQIyphhOM5VmSuZP-kDtYHpXUCRCRgayBPYepTUQ4o4D0HAvqO7mUcu67i8jcqKfsQ61ETRB-PWlqzwXkizWkYL5Iiid9h0krGtMBFovc0rtodV5E5NnfZNA4DZ34lZZMPfUdbiILvWPea-kKSmXlU_rLtcQSgZGvFlu_oq9EZFxyHmJcrgqP4TT1WmQHJAVNdejMUdCFR_hIwA0WYMm8B7i2fmsg1_fEvW_s1ClPoT58b9Hl5rMh43r7Fva6gKCOe5IlAb1zHDWMe_dudteVMi32S1hNQvioOYr3ulfLxfjtD39WJilyEAU54o8-qEhoY17kwZb7paH1eVj9FbfqpMDQmANAAX2Z-VtEMiWcfeQCx7Oz6DKBY9wyWIVZgQPDc5Ez0PHWpD0C_n6YXUN2S5fgnTJzY9b44VShHECPEYAQp-wP_j_2GUJSulmIlDBYEF64uCW8E3tTKaeOXbdXhPKchr86sVMVrjl8QvjJcgXsh3cyFJV3I3ZgUGVfEEMffFTHcagKVQoVgGgu3wZfkZyJcdR8W5TP2l2_BTzBYBZCJMamFqugsE8Ea7ubLL8WyOt2JEqGWJfWOAZ7pnLSwoTuYSlUsbUKHmtTifstBKFo_p0-S1MwURgeaEzVwuAGUZqKZ_T-okqM2LjbhW3oUXc -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/TestCertificate.cer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kreactive/JSONWebToken/bae13a4eb7ab84406abb3d44e2b8b455cbd6383c/JSONWebTokenTests/Samples/TestCertificate.cer -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/all_claim_valid_1.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhbnRvaW5lIiwiaXNzIjoia3JlYWN0aXZlIiwianRpIjoiMTIzNDU2Nzg5IiwiZXhwIjoxNzM1Njg5NjAwLCJpYXQiOjE0NDgzNzE3MDQsIm5iZiI6MCwiYXVkIjoidGVzdC1hcHAifQ. -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/all_claim_valid_2.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhbnRvaW5lIiwiaXNzIjoia3JlYWN0aXZlIiwianRpIjoiMTIzNDU2Nzg5IiwiZXhwIjoxNzM1Njg5NjAwLCJpYXQiOjE0NDgzNzE3MDQsIm5iZiI6MCwiYXVkIjpbInRlc3QtYXBwIiwidGVzdC1hcHAyIl19. -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/all_claim_valid_2_signed.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbnRvaW5lIiwiaXNzIjoia3JlYWN0aXZlIiwianRpIjoiMTIzNDU2Nzg5IiwiZXhwIjoxNzM1Njg5NjAwLCJpYXQiOjE0NDgzNzE3MDQsIm5iZiI6MCwiYXVkIjpbInRlc3QtYXBwIiwidGVzdC1hcHAyIl19.zIgtjHICrDqIJ3EeIPMhKiLomV4VtqzeBCG1bZzoybk -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/empty.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.e30. -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/empty2.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiIDogbnVsbCwic3ViIiA6IG51bGwsImF1ZCIgOiBudWxsLCAiZXhwIiA6IG51bGwsICJuYmYiIDogbnVsbCwgImp0aSIgOiBudWxsfQ. -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/identity.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kreactive/JSONWebToken/bae13a4eb7ab84406abb3d44e2b8b455cbd6383c/JSONWebTokenTests/Samples/identity.p12 -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_alg.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSUzkwMDAiLCAidHlwIiA6ICJKV1QifQ.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Q6CM1qIz2WTgTlhMzpFL8jI8xbu9FFfj5DY_bGVY98Y -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_aud_format.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJhdWQiOjE3NjI4MTkyMDB9. -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_exp_format.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOiIxMS8xMS8yMDI1In0. -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_expired.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOjE0NDg0NjU0Nzh9. -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_header_base64.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJIUzI1NiIsInR5c+CI6I&kp/XVCJ9==.eyJzdWIiOiJhbnRvaW5lIiwiaXNzIjoia3JlYWN0aXZlIiwianRpIjoiMTIzNDU2Nzg5IiwiZXhwIjoxNzM1Njg5NjAwLCJpYXQiOjE0NDgzNzE3MDQsIm5iZiI6MCwiYXVkIjpbInRlc3QtYXBwIiwidGVzdC1hcHAyIl19.zIgtjHICrDqIJ3EeIPMhKiLomV4VtqzeBCG1bZzoybk -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_header_json.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSUzMidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3OGFBHDkwIiwibmFtZSI6IkpvaG4gRG9lIn0.k2zvPaQjdbW-uZ6OBreIqHI9HeckcAjzZCQV8awkZ9iuscJJjrkt_N3aoEkVwG-i -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_header_json_structure.jwt: -------------------------------------------------------------------------------- 1 | WyJiYWQiLCJqc29uIiwic3RydWN0dXJlIl0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.k2zvPaQjdbW-uZ6OBreIqHI9HeckcAjzZCQV8awkZ9iuscJJjrkt_N3aoEkVwG-i -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_iat_format.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpYXQiOiIxMS8xMS8yMDI1In0. -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_iss_format.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOjE3NjI4MTkyMDB9. -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_jti_format.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJqdGkiOjE3NjI4MTkyMDB9. -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_missing_alg.jwt: -------------------------------------------------------------------------------- 1 | eyJ0eXAiIDogIkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Q6CM1qIz2WTgTlhMzpFL8jI8xbu9FFfj5DY_bGVY98Y -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_nbf_format.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJuYmYiOiIxMS8xMS8yMDI1In0. -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_nbf_immature.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJuYmYiOjE3NjI4MTkyMDB9. -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_payload_base64.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbnRvaW5/lIiwiaXNzIjoia3JlYWN0aXZl&IiwianRpIjoiMTIzNDU2Nzg5IiwiZXhwIjoxNzM1Njg5NjAwLCJpYXQiOjE0NDgzNzE3MDQsIm5iZiI6MCwiYXVkIjpbInRlc3QtYX/BwIiwidGVzdC1hcHAyIl+19.zIgtjHICrDqIJ3EeIPMhKiLomV4VtqzeBCG1bZzoybk -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_payload_json.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4.k2zvPaQjdbW-uZ6OBreIqHI9HeckcAjzZCQV8awkZ9iuscJJjrkt_N3aoEkVwG-i -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_payload_json_structure.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.WyJiYWQiLCJqc29uIiwic3RydWN0dXJlIl0.Q6CM1qIz2WTgTlhMzpFL8jI8xbu9FFfj5DY_bGVY98Y -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_signature_base64.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.tJqJJDJAGQ/tMhBndUjdxIxmQ/r4LCEotyDiMMK5qsg8rjhO8Dmb4gFmdZJZjfQZBq4c1/YAy3kVa/7eoxl4fdO3LkSiWCdBHa7nrU3aqXVm39CPZfqsNVf0CFdUloradZN7O1uf3V/OaBa6eEcr/PCl8S-bIqI2UYw23yLQ09-moElPQScPoVdiiKAiBzdt2iEEuDmw3EFvb-qQ20EdKDAXRMMC6kIommaHi1Lo36ckqUqgo1GUPy9nXJfluuXnuv/vKaaYx/fcpL8p8uIvtNVAJKtYjMiykYtUiYN7Wb/AbesqthFN00TQd9d7JSje-Wqp6i/1nyv6pxZspf3IExitqZ1yCELP8jYWw3dyN16CfMZxrCGNSuvlF2mkStnryTE1K7i2EU4CnTTuSrAM-b7gxNtlca91evKwiGW6rzCel3rNLG2RGlcdcok1i6x6UzvMeOmlmmKFncZF14rtv2tEBE/SCTolTMZNQErguZI60RFaJH9dhscyBRVP6/nxG4f-teYV8lFI2dkl8EYDA5lz13fhPoax0E4f8OG7848F0sR2&Ov3m1E9K5gONA37L3iqN5TyjixcUAoWQsVsIMjAJOjbsU3Me7ry3fFplVPqEkwdNdOOYRf-AfDHCWuBMqmjyn04nPeQh5f2pJqr/9Vol8eITS8bW5ZFdZbmmBxQ -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_structure.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.tJqJJDJAGQ_tMhBndUjdxIxmQ_r4LCEotyDiMMK5qsg8rjhO8Dmb4gFmdZJZjfQZBq4c1_YAy3kVa_7eoxl4fdO3LkSiWCdBHa7nrU3aqXVm39CPZfqsNVf0CFdUloradZN7O1uf3V_OaBa6eEcr_PCl8S-bIqI2UYw23yLQ09-moElPQScPoVdiiKAiBzdt2iEEuDmw3EFvb-qQ20EdKDAXRMMC6kIommaHi1Lo36ckqUqgo1GUPy9nXJfluuXnuv_vKaaYx_fcpL8p8uIvtNVAJKtYjMiykYtUiYN7Wb_AbesqthFN00TQd9d7JSje-Wqp6i_1nyv6pxZspf3IExitqZ1yCELP8jYWw3dyN16CfMZxrCGNSuvlF2mkStnryTE1K7i2EU4CnTTuSrAM-b7gxNtlca91evKwiGW6rzCel3rNLG2RGlcdcok1i6x6UzvMeOmlmmKFncZF14rtv2tEBE_SCTolTMZNQErguZI60RFaJH9dhscyBRVP6_nxG4f-teYV8lFI2dkl8EYDA5lz13fhPoax0E4f8OG7848F0sR2Ov3m1E9K5gONA37L3iqN5TyjixcUAoWQsVsIMjAJOjbsU3Me7ry3fFplVPqEkwdNdOOYRf-AfDHCWuBMqmjyn04nPeQh5f2pJqr_9Vol8eITS8bW5ZFdZbmmBxQ -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_structure_2.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0tJqJJDJAGQ_tMhBndUjdxIxmQ_r4LCEotyDiMMK5qsg8rjhO8Dmb4gFmdZJZjfQZBq4c1_YAy3kVa_7eoxl4fdO3LkSiWCdBHa7nrU3aqXVm39CPZfqsNVf0CFdUloradZN7O1uf3V_OaBa6eEcr_PCl8S-bIqI2UYw23yLQ09-moElPQScPoVdiiKAiBzdt2iEEuDmw3EFvb-qQ20EdKDAXRMMC6kIommaHi1Lo36ckqUqgo1GUPy9nXJfluuXnuv_vKaaYx_fcpL8p8uIvtNVAJKtYjMiykYtUiYN7Wb_AbesqthFN00TQd9d7JSje-Wqp6i_1nyv6pxZspf3IExitqZ1yCELP8jYWw3dyN16CfMZxrCGNSuvlF2mkStnryTE1K7i2EU4CnTTuSrAM-b7gxNtlca91evKwiGW6rzCel3rNLG2RGlcdcok1i6x6UzvMeOmlmmKFncZF14rtv2tEBE_SCTolTMZNQErguZI60RFaJH9dhscyBRVP6_nxG4f-teYV8lFI2dkl8EYDA5lz13fhPoax0E4f8OG7848F0sR2Ov3m1E9K5gONA37L3iqN5TyjixcUAoWQsVsIMjAJOjbsU3Me7ry3fFplVPqEkwdNdOOYRf-AfDHCWuBMqmjyn04nPeQh5f2pJqr_9Vol8eITS8bW5ZFdZbmmBxQ -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_sub_format.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOjE3NjI4MTkyMDB9. -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/invalid_typ.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXUyJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Q6CM1qIz2WTgTlhMzpFL8jI8xbu9FFfj5DY_bGVY98Y -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/private2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/private_key_pem_to_p12_identity.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | INPUT=$1 4 | DIRECTORY=`dirname "$INPUT"` 5 | FILENAME=`basename "$INPUT"` 6 | TARGET_BASE_PATH="$DIRECTORY"/"${FILENAME%%.*}" 7 | echo $1 8 | echo $TARGET_BASE_PATH 9 | echo "$TARGET_BASE_PATH".csr 10 | echo "$TARGET_BASE_PATH".crt 11 | 12 | #Create a certificate signing request with the private key 13 | openssl req -new -key $1 -out "$TARGET_BASE_PATH".csr 14 | 15 | #Create a self-signed certificate with the private key and signing request 16 | openssl x509 -req -days 3650 -in "$TARGET_BASE_PATH".csr -signkey $INPUT -out "$TARGET_BASE_PATH".crt 17 | 18 | #Convert the certificate to DER format: the certificate contains the public key 19 | openssl x509 -outform der -in "$TARGET_BASE_PATH".crt -out "$TARGET_BASE_PATH".der 20 | 21 | #Export the private key and certificate to p12 file 22 | openssl pkcs12 -export -out "$TARGET_BASE_PATH".p12 -inkey $INPUT -in "$TARGET_BASE_PATH".crt -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/public.exponent: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/public.modulus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kreactive/JSONWebToken/bae13a4eb7ab84406abb3d44e2b8b455cbd6383c/JSONWebTokenTests/Samples/public.modulus -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIICCgKCAgEApmlQ3ER3KIBy8kQj6rGwYSb73qVw+H1C27QtZT05jahaEMhf9kqw 3 | dhduqk/KpdRi/ghy8r1fhee5W8yrEZbEreQBG4BCCM4T6do6Xl53gU0JNOznx7sm 4 | DfZsgtCpjbnf0wuiY5sqWgWoB7IDkQwq/V/ekBPZ2J97m43tBTP6J9pMYmU/pQJG 5 | N9jxNNtum8W84d9mZWm19Kar3i3KmDi7bJSAwZGXS9MKTPfl76jHVjsZ94iQEZBT 6 | NjoYUVc/E5y3/vVroq3NfE6dh4j0dfRZhfz6HJQtqy/dHNMu14chTvQFzN+HuFRU 7 | a0swEEsNjQqFmqRkB+sMDfw1mbjP3fb46pcnWdXHQyJP0q3vePLwanvwI7u32Uly 8 | aXe9bWlb6nuzBlqfwGzm7oT021yHUtRmK3Gr5/nUWwJzjvzEOn5hvnUUU37cw9WB 9 | b+itd+r9y469tBW2vZFyIodNSzgQ5/GCPbtfRjPKZ+Lfev3G0kjBRDKhcSFc3oak 10 | qcWdBC9C1KLKFYwZMRuE3wu7sQMk4PkTg5xnnUn8m9462DljfkieNAZzBwdIbPCG 11 | tu/dhQhaJcz/Dq0FgIkwoLXYzJvzgPuZq8MqHA/eJnssELvWRLoWLncyQz1giUgZ 12 | vU4v+0xcMuMqQA+TsnIAEhNG8T8hsrVqD3dQvkbaWsgCCQY0EkjHeZUCAwEAAQ== 13 | -----END RSA PUBLIC KEY----- -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/public2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIICCgKCAgEAz5wSeoffVD3coUZQYZrMGuZgtloPioEX6VUrUhjYkeKsR27vph4B 3 | QhM0Vj8t/Ej2JF4BkREV2eHnWisJoL+ZfS4LxSz0Rkc4eKhJiol12XVOtE2xxrVD 4 | cmF4HMIs+k5Nl4vyIfhGqAlrKxPDJD2hZ3ROAscMfLwmdZU7IwgZpr02hbbVZ1DA 5 | 3lzt6f6r9g/EMkFNl51Bxc9WIV7ymgQrFCeH/JJydiQ5wDS133m1aeNImksIA9O/ 6 | bjifVc87+FPiK2dU8I1DlRWKBw0+YG0tW9ShDuG/jVdDHtWqPamGVu12sMXugcw3 7 | vVk6xrd5cdFjKd/ToVzfBpjXftyRdWwFduQQOv+05sLxaDOH+fF20PkBKgYWYmkA 8 | kzokZo6E5Ofy8Vvzf7UyYD+1AbFJ2rZkGh1LUM2OcaqH+LYMWVZBGESIkH0DOlwA 9 | k7B78yubIWV1295Y0tuxc47Fu+fMLIZQKx6D0MXpYwp5kK9ITBWYfVpXudFv5zjX 10 | WrcIM/2UdPhpxNVHS6vBg3c14tVXExARIKNux650f/P/rL6R/6O6R8Nzr2+u2VAn 11 | yCAO7BuVo006EId9sJe0OHEKjvJskXCfLMhscan/JOu0/r9C8TwVvaPI0LLETcIl 12 | /5ip9Ht4NWuOSQ1oQ8/cEZx7v2+SkLMYDD/FjDdvycXDtix/OM0Rg28CAwEAAQ== 13 | -----END RSA PUBLIC KEY----- -------------------------------------------------------------------------------- /JSONWebTokenTests/Samples/valid_missing_typ.jwt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSUzM4NCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Q6CM1qIz2WTgTlhMzpFL8jI8xbu9FFfj5DY_bGVY98Y -------------------------------------------------------------------------------- /JSONWebTokenTests/TestUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestUtils.swift 3 | // JSONWebToken 4 | // 5 | // Created by Antoine Palazzolo on 19/11/15. 6 | // Copyright © 2015 Antoine Palazzolo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import JSONWebToken 11 | 12 | func ReadRawSampleWithName(_ name : String) -> String { 13 | let path = Bundle(for: HMACTests.self).path(forResource: name, ofType: "jwt")! 14 | return try! String(contentsOfFile: path, encoding: String.Encoding.utf8) 15 | } 16 | func ReadSampleWithName(_ name : String) -> JSONWebToken { 17 | return try! JSONWebToken(string : ReadRawSampleWithName(name)) 18 | } 19 | 20 | var SamplePublicKey : RSAKey = { 21 | return SampleIdentity.publicKey 22 | 23 | }() 24 | 25 | let SamplePrivateKey : RSAKey = { 26 | return SampleIdentity.privateKey 27 | }() 28 | 29 | let SampleIdentity : (publicKey : RSAKey,privateKey : RSAKey) = { 30 | let path = Bundle(for: HMACTests.self).path(forResource: "identity", ofType: "p12")! 31 | let p12Data = try! Data(contentsOf: URL(fileURLWithPath: path)) 32 | return try! RSAKey.keysFromPkcs12Identity(p12Data, passphrase : "1234") 33 | }() 34 | 35 | let SamplePayload : JSONWebToken.Payload = { 36 | var payload = JSONWebToken.Payload() 37 | payload.issuer = "1234567890" 38 | payload["name"] = "John Doe" 39 | return payload 40 | }() 41 | -------------------------------------------------------------------------------- /KTVJSONWebToken.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = 'KTVJSONWebToken' 4 | s.version = '2.1.0' 5 | s.summary = 'Swift lib for decoding, validating, signing and verifying JWT' 6 | s.homepage = "https://github.com/kreactive/JSONWebToken" 7 | s.license = 'MIT' 8 | s.author = { 'Kreactive' => 'https://github.com/kreactive' } 9 | s.source = { :git => "https://github.com/kreactive/JSONWebToken.git", :tag => "version2.1.0"} 10 | 11 | 12 | s.ios.deployment_target = '8.0' 13 | s.requires_arc = true 14 | s.framework = 'Security' 15 | s.source_files = 'JSONWebToken/*.{swift,h,m}' 16 | s.exclude_files = 'JSONWebToken/JSONWebToken.h' 17 | s.public_header_files = 'JSONWebToken/NSData+SHA.h','JSONWebToken/NSData+HMAC.h' 18 | s.swift_version = '4.1' 19 | end 20 | 21 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Kreactive 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSONWebToken 2 | Swift library for decoding, validating, signing and verifying JWT 3 | 4 | # Features 5 | 6 | - Verify and sign : 7 | - HMAC `HS256` `HS384` `HS512` 8 | - RSASSA-PKCS1-v1_5 `RS256` `RS384` `RS384` 9 | - Validate (optionally) all [registered claims](https://tools.ietf.org/html/rfc7519#section-4.1) : 10 | - Issuer `iss` 11 | - Subject `sub` 12 | - Audience `aud` 13 | - Expiration Time `exp` 14 | - Not Before `nbf` 15 | - Issued At `iat` 16 | - JWT ID `jti` 17 | - No external dependencies : **CommonCrypto** and **Security** framework are used for signing and verifying 18 | - Extensible : add your own claim validator and sign operations 19 | 20 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 21 | 22 | # Usage 23 | 24 | ## Decode & Validation 25 | 26 | ```swift 27 | import JSONWebToken 28 | 29 | let rawJWT : String 30 | let jwt : JSONWebToken = try JSONWebToken(string : rawJWT) 31 | 32 | //create the validator by combining other validators with the & or | operator 33 | let validator = RegisteredClaimValidator.expiration & 34 | RegisteredClaimValidator.notBefore.optional & 35 | HMACSignature(secret: "secret".dataUsingEncoding(NSUTF8StringEncoding)!, hashFunction: .SHA256) 36 | /* 37 | - not expired 38 | - can be used now (optional : a jwt without nbf will be valid) 39 | - signed with HS256 and the key "secret" 40 | */ 41 | let validationResult = validator.validateToken(jwt) 42 | guard case ValidationResult.Success = validationResult else { return } 43 | 44 | //use the token and access the payload 45 | let issuer : String? = jwt.payload.issuer 46 | let customClaim = jwt.payload["customClaim"] as? String 47 | ``` 48 | ## Sign 49 | 50 | ```swift 51 | import JSONWebToken 52 | 53 | //build the payload 54 | var payload = JSONWebToken.Payload() 55 | payload.issuer = "http://kreactive.com" 56 | payload.subject = "antoine" 57 | payload.audience = ["com.kreactive.app"] 58 | payload.expiration = NSDate().dateByAddingTimeInterval(300) 59 | payload.notBefore = NSDate() 60 | payload.issuedAt = NSDate() 61 | payload.jwtIdentifier = NSUUID().UUIDString 62 | payload["customClaim"] = "customClaim" 63 | 64 | //use HS256 to sign the token 65 | let signer = HMACSignature(secret: "secret".dataUsingEncoding(NSUTF8StringEncoding)!, hashFunction: .SHA256) 66 | 67 | //build the token, signer is optional 68 | let jwt = try JSONWebToken(payload : payload, signer : signer) 69 | let rawJWT : String = jwt.rawString 70 | ``` 71 | 72 | ## RSASSA-PKCS1-v1_5 Signature 73 | 74 | #### Keys 75 | Keys are represented by the `RSAKey` struct, wrapping a `SecKeyRef`. 76 | The preferred way of importing **public keys** is to use a `DER-encoded X.509` certificate (.cer), and for **private keys** a `PKCS#12` (.p12) identity. 77 | It's also possible to import raw representation of keys (X509, public pem, modulus/exponent ...) by using a keychain item import side effect. 78 | ```swift 79 | let certificateData : NSData = //DER-encoded X.509 certificate 80 | let publicKey : RSAKey = try RSAKey(certificateData : certificateData) 81 | ``` 82 | 83 | ```swift 84 | let p12Data : NSData //PKCS #12–formatted identity data 85 | let identity : (publicKey : RSAKey, privateKey : RSAKey) = try RSAKey.keysFromPkcs12Identity(p12Data, passphrase : "pass") 86 | ``` 87 | 88 | ```swift 89 | let keyData : NSData 90 | //import key into the keychain 91 | let key : RSAKey = try RSAKey.registerOrUpdateKey(keyData, tag : "keyTag") 92 | ``` 93 | 94 | ```swift 95 | let modulusData : NSData 96 | let exponentData : NSData 97 | //import key into the keychain 98 | let key : RSAKey = try RSAKey.registerOrUpdateKey(modulus : modulusData, exponent : exponentData, tag : "keyTag") 99 | ``` 100 | 101 | Retrieve or delete key from the keychain : 102 | ```swift 103 | //get registered key 104 | let key : RSAKey? = RSAKey.registeredKeyWithTag("keyTag") 105 | //remove 106 | RSAKey.removeKeyWithTag("keyTag") 107 | ``` 108 | 109 | A large part of the raw key import code is copied from the [Heimdall](https://github.com/henrinormak/Heimdall) library. 110 | #### Verify 111 | Use `RSAPKCS1Verifier` as validator to verify token signature : 112 | 113 | ```swift 114 | let jwt : JSONWebToken 115 | let publicKey : RSAKey 116 | let validator = RegisteredClaimValidator.expiration & 117 | RegisteredClaimValidator.notBefore.optional & 118 | RSAPKCS1Verifier(key : publicKey, hashFunction: .SHA256) 119 | 120 | let validationResult = validator.validateToken(jwt) 121 | ... 122 | ``` 123 | #### Sign 124 | Use `RSAPKCS1Signer` to generate signed token: 125 | 126 | ```swift 127 | let payload : JSONWebToken.Payload 128 | let privateKey : RSAKey 129 | 130 | let signer = RSAPKCS1Signer(hashFunction: .SHA256, key: privateKey) 131 | let jwt = try JSONWebToken(payload : payload, signer : signer) 132 | let rawJWT : String = jwt.rawString 133 | ... 134 | ``` 135 | # Validation 136 | Validators (signature and claims) implement the protocol `JSONWebTokenValidatorType` 137 | ```swift 138 | public protocol JSONWebTokenValidatorType { 139 | func validateToken(token : JSONWebToken) -> ValidationResult 140 | } 141 | ``` 142 | 143 | Implementing this protocol on any `class` or `struct` allows it to be combined with other validator using the `|` or `&` operator. 144 | The validation method returns a `ValidationResult` : 145 | 146 | ```swift 147 | public enum ValidationResult { 148 | case Success 149 | case Failure(ErrorType) 150 | 151 | public var isValid : Bool 152 | } 153 | ``` 154 | 155 | ## Claim validation 156 | You can implement a claim validator with the `ClaimValidator` struct : 157 | ```swift 158 | public struct ClaimValidator : JSONWebTokenValidatorType { 159 | } 160 | ``` 161 | 162 | ```swift 163 | let validator : ClaimValidator = ClaimValidator(key: "customClaim", transform: { (jsonValue : AnyObject) throws -> Int in 164 | guard let numberValue = jsonValue as? NSNumber else { 165 | throw ClaimValidatorError(message: "customClaim value \(jsonValue) is not the expected Number type") 166 | } 167 | return numberValue.integerValue 168 | }).withValidator { 1..<4 ~= $0 } 169 | ``` 170 | 171 | All registered claims validators are implemented : 172 | 173 | - `RegisteredClaimValidator.issuer` : `iss` claim is defined and is a `String` 174 | - `RegisteredClaimValidator.subject` : `sub` claim is defined and is a `String` 175 | - `RegisteredClaimValidator.audience` : `aud` claim is defined and is a `String` or `[String]` 176 | - `RegisteredClaimValidator.expiration` : `exp` claim is defined, is an `Integer` transformable to `NSDate`, and is after current date 177 | - `RegisteredClaimValidator.notBefore` : `nbf` claim is defined, is an `Integer` transformable to `NSDate`, and is before current date 178 | - `RegisteredClaimValidator.issuedAt` : `iat` claim is defined, is an `Integer` transformable to `NSDate` 179 | - `RegisteredClaimValidator.jwtIdentifier` : `jti` claim is defined and is a `String` 180 | 181 | And it can be extended : 182 | ```swift 183 | let myIssuerValidator = RegisteredClaimValidator.issuer.withValidator { $0 == "kreactive" } 184 | ``` 185 | # Unsupported signature 186 | 187 | ## Verify 188 | Implement the `SignatureValidator` protocol on any `class` or `struct` to add and unsupported signature algorithm validator. 189 | 190 | ```swift 191 | public protocol SignatureValidator : JSONWebTokenValidatorType { 192 | func canVerifyWithSignatureAlgorithm(alg : SignatureAlgorithm) -> Bool 193 | func verify(input : NSData, signature : NSData) -> Bool 194 | } 195 | ``` 196 | 197 | ## Sign 198 | Implement the `TokenSigner` protocol on any `class` or `struct` to sign token with unsupported signature algorithm. 199 | 200 | ```swift 201 | public protocol TokenSigner { 202 | var signatureAlgorithm : SignatureAlgorithm {get} 203 | func sign(input : NSData) throws -> NSData 204 | } 205 | ``` 206 | 207 | # Test 208 | 209 | Test [samples](https://github.com/kreactive/JSONWebToken/tree/master/JSONWebTokenTests/Samples) are [generated](https://github.com/kreactive/JSONWebToken/blob/master/JSONWebTokenTests/Samples/GenerateSample.py) using [pyjwt](https://github.com/jpadilla/pyjwt) Python library 210 | -------------------------------------------------------------------------------- /TestContainer/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TestContainer 4 | // 5 | // Created by Antoine Palazzolo on 28/09/2016. 6 | // Copyright © 2016 Antoine Palazzolo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /TestContainer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /TestContainer/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TestContainer/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /TestContainer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /TestContainer/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // TestContainer 4 | // 5 | // Created by Antoine Palazzolo on 28/09/2016. 6 | // Copyright © 2016 Antoine Palazzolo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | 23 | 24 | } 25 | 26 | --------------------------------------------------------------------------------