Repository navigation
Expand file tree
/
Copy pathJSONWebToken.swift
More file actions
230 lines (203 loc) · 8.09 KB
/
Copy pathJSONWebToken.swift
File metadata and controls
230 lines (203 loc) · 8.09 KB
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
//
// JSONWebToken.swift
// JSONWebToken
//
// Created by Antoine Palazzolo on 17/11/15.
//
import Foundation
public struct JSONWebToken {
public indirect enum Error : Swift.Error {
case badTokenStructure
case cannotDecodeBase64Part(JSONWebToken.Part,String)
case invalidJSON(JSONWebToken.Part,Swift.Error)
case invalidJSONStructure(JSONWebToken.Part)
case typeIsNotAJSONWebToken
case invalidSignatureAlgorithm(String)
case missingSignatureAlgorithm
}
public enum Part {
case header
case payload
case signature
}
public struct Payload {
public enum RegisteredClaim : String {
case Issuer = "iss"
case Subject = "sub"
case Audience = "aud"
case ExpirationTime = "exp"
case NotBefore = "nbf"
case IssuedAt = "iat"
case JWTIdentifier = "jti"
}
var jsonPayload : [String : Any]
fileprivate init(jsonPayload : [String : Any]) {
self.jsonPayload = jsonPayload
}
public init() {
jsonPayload = Dictionary()
}
public subscript(key : String) -> Any? {
get {
let result = jsonPayload[key]
switch result {
case .some(let value) where value is NSNull:
return nil
case .some(_):
return result
case .none:
return nil
}
}
set {
if newValue == nil || newValue is NSNull {
jsonPayload.removeValue(forKey: key)
} else {
jsonPayload[key] = newValue
}
}
}
fileprivate subscript(registeredClaim : RegisteredClaim) -> Any? {
get {
return self[registeredClaim.rawValue]
}
set {
return self[registeredClaim.rawValue] = newValue
}
}
public var issuer : String? {
get {
return (((try? self[.Issuer].map(RegisteredClaimValidator.issuer.transform)) as String??)) ?? nil
}
set {
self[.Issuer] = newValue
}
}
public var subject : String? {
get {
return (((try? self[.Subject].map(RegisteredClaimValidator.subject.transform)) as String??)) ?? nil
}
set {
self[.Subject] = newValue
}
}
public var audience : [String] {
get {
return (try? self[.Audience].map(RegisteredClaimValidator.audience.transform) ?? []) ?? []
}
set {
switch newValue.count {
case 0:
self[.Audience] = nil
case 1:
self[.Audience] = newValue[0]
default:
self[.Audience] = newValue
}
}
}
fileprivate static func jsonClaimValueFromDate(_ date : Date?) -> NSNumber? {
return date.map { NSNumber(value: Int64($0.timeIntervalSince1970)) }
}
public var expiration : Date? {
get {
return (((try? self[.ExpirationTime].map(RegisteredClaimValidator.expiration.transform)) as Date??)) ?? nil
}
set {
self[.ExpirationTime] = Payload.jsonClaimValueFromDate(newValue)
}
}
public var notBefore : Date? {
get {
return (((try? self[.NotBefore].map(RegisteredClaimValidator.notBefore.transform)) as Date??)) ?? nil
}
set {
self[.NotBefore] = Payload.jsonClaimValueFromDate(newValue)
}
}
public var issuedAt : Date? {
get {
return (((try? self[.IssuedAt].map(RegisteredClaimValidator.issuedAt.transform)) as Date??)) ?? nil
}
set {
self[.IssuedAt] = Payload.jsonClaimValueFromDate(newValue)
}
}
public var jwtIdentifier : String? {
get {
return (((try? self[.JWTIdentifier].map(RegisteredClaimValidator.jwtIdentifier.transform)) as String??)) ?? nil
}
set {
self[.JWTIdentifier] = newValue
}
}
}
public let signatureAlgorithm : SignatureAlgorithm
public let payload : Payload
let base64Parts : (header : String,payload : String, signature : String)
public init(string input: String) throws {
let parts = input.components(separatedBy: ".")
guard parts.count == 3 else { throw Error.badTokenStructure }
self.base64Parts = (parts[0],parts[1],parts[2])
guard let headerData = Data(jwt_base64URLEncodedString: base64Parts.header, options: []) else {
throw Error.cannotDecodeBase64Part(.header,base64Parts.header)
}
guard let payloadData = Data(jwt_base64URLEncodedString: base64Parts.payload, options: []) else {
throw Error.cannotDecodeBase64Part(.payload,base64Parts.payload)
}
guard Data(jwt_base64URLEncodedString: base64Parts.signature, options: []) != nil else {
throw Error.cannotDecodeBase64Part(.signature,base64Parts.signature)
}
let jsonHeader = try JSONWebToken.jwtJSONFromData(headerData,part: .header)
guard (jsonHeader["typ"] as? String).map({$0.uppercased() == "JWT"}) ?? true else {
throw Error.typeIsNotAJSONWebToken
}
guard let signatureAlgorithm = try (jsonHeader["alg"] as? String).map(SignatureAlgorithm.init) else {
throw Error.missingSignatureAlgorithm
}
self.signatureAlgorithm = signatureAlgorithm
let jsonPayload = try JSONWebToken.jwtJSONFromData(payloadData,part: .payload)
self.payload = Payload(jsonPayload: jsonPayload)
}
fileprivate static func jwtJSONFromData(_ data : Data, part : JSONWebToken.Part) throws -> [String : Any] {
let json : Any
do {
json = try JSONSerialization.jsonObject(with: data, options: [])
} catch {
throw Error.invalidJSON(part,error)
}
guard let result = json as? [String : Any] else {
throw Error.invalidJSONStructure(part)
}
return result
}
public init(payload : Payload, signer : TokenSigner? = nil) throws {
self.signatureAlgorithm = signer?.signatureAlgorithm ?? SignatureAlgorithm.none
self.payload = payload
let header = ["alg" : self.signatureAlgorithm.jwtIdentifier , "typ" : "JWT"]
let headerBase64 = try JSONSerialization.data(withJSONObject: header, options: []).jwt_base64URLEncodedStringWithOptions([])
let payloadBase64 = try JSONSerialization.data(withJSONObject: payload.jsonPayload, options: []).jwt_base64URLEncodedStringWithOptions([])
let signatureInput = headerBase64 + "." + payloadBase64
let signature = try signer.map {
try $0.sign(signatureInput.data(using: String.Encoding.utf8)!)
} ?? Data()
let signatureBase64 = signature.jwt_base64URLEncodedStringWithOptions([])
self.base64Parts = (headerBase64,payloadBase64,signatureBase64)
}
public func decodedDataForPart(_ part : Part) -> Data {
switch part {
case .header:
return Data(jwt_base64URLEncodedString: base64Parts.header, options: [])!
case .payload:
return Data(jwt_base64URLEncodedString: base64Parts.payload, options: [])!
case .signature:
return Data(jwt_base64URLEncodedString: base64Parts.signature, options: [])!
}
}
public var rawString : String {
return "\(base64Parts.header).\(base64Parts.payload).\(base64Parts.signature)"
}
public var rawData : Data {
return self.rawString.data(using: String.Encoding.utf8)!
}
}