├── .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 | [](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 |
--------------------------------------------------------------------------------