this repo has no description
1import JOSESwift
2
3class JoseUtil: NSObject {
4 private static func headerStringToHeader(_ headerString: String) -> JWSHeader? {
5 guard let headerData = headerString.data(using: .utf8) else {
6 return nil
7 }
8 return JWSHeader(headerData)
9 }
10
11 private static func payloadStringToPayload(_ payloadString: String) -> Payload? {
12 guard let payloadData = payloadString.data(using: .utf8) else {
13 return nil
14 }
15 return Payload(payloadData)
16 }
17
18 static func createJwt(header: String, payload: String, jwk: SecKey) throws -> String {
19 guard let header = headerStringToHeader(header) else {
20 throw ExpoAtprotoAuthError.invalidHeader("could not parse header string")
21 }
22
23 guard let payload = payloadStringToPayload(payload) else {
24 throw ExpoAtprotoAuthError.invalidPayload("could not parse payload string")
25 }
26
27 let signer = Signer(signingAlgorithm: .ES256, key: jwk)
28
29 guard let signer = signer else {
30 throw ExpoAtprotoAuthError.nullSigner
31 }
32
33 let jws = try JWS(header: header, payload: payload, signer: signer)
34
35 return jws.compactSerializedString
36 }
37
38 static func verifyJwt(token: String, jwk: SecKey, options: VerifyOptions) throws -> VerifyResponse {
39 guard let jws = try? JWS(compactSerialization: token),
40 let verifier = Verifier(verifyingAlgorithm: .ES256, key: jwk),
41 let validation = try? jws.validate(using: verifier)
42 else {
43 throw ExpoAtprotoAuthError.invalidJwk
44 }
45
46 let header = validation.header
47 let payload = String(data: validation.payload.data(), encoding: .utf8)
48 guard let payload = payload else {
49 throw ExpoAtprotoAuthError.invalidPayload("unable to parse payload")
50 }
51
52 var protectedHeader: [String: Any] = [:]
53 protectedHeader["alg"] = "ES256"
54 if header.jku != nil {
55 protectedHeader["jku"] = header.jku?.absoluteString
56 }
57 if header.kid != nil {
58 protectedHeader["kid"] = header.kid
59 }
60 if header.typ != nil {
61 protectedHeader["typ"] = header.typ
62 }
63 if header.cty != nil {
64 protectedHeader["cty"] = header.cty
65 }
66 if header.crit != nil {
67 protectedHeader["crit"] = header.crit
68 }
69
70 if let typ = options.typ {
71 if header.typ != typ {
72 throw ExpoAtprotoAuthError.invalidPayload("typ mismatch")
73 }
74 }
75
76 let claims = try JSONSerialization.jsonObject(with: validation.payload.data(), options: []) as? [String: Any]
77
78 if let requiredClaims = options.requiredClaims {
79 try requiredClaims.forEach { c in
80 if claims?[c] == nil {
81 throw ExpoAtprotoAuthError.invalidPayload("required claim \(c) missing")
82 }
83 }
84 }
85
86 if let audience = options.audience {
87 if claims?["aud"] as? String != audience {
88 throw ExpoAtprotoAuthError.invalidPayload("audience mismatch")
89 }
90 }
91
92 if let subject = options.subject {
93 if claims?["sub"] as? String != subject {
94 throw ExpoAtprotoAuthError.invalidPayload("subject mismatch")
95 }
96 }
97
98 if let checkTolerance = options.clockTolerance {
99 let now = Date()
100 let expiryDate: Date
101 if let expiryString = claims?["exp"] as? String {
102 let formatter = ISO8601DateFormatter()
103 expiryDate = formatter.date(from: expiryString)!
104 } else {
105 throw ExpoAtprotoAuthError.invalidPayload("expiry missing")
106 }
107 if expiryDate < now - checkTolerance {
108 throw ExpoAtprotoAuthError.invalidPayload("token expired")
109 }
110 }
111
112 if let maxTokenAge = options.maxTokenAge {
113 let now = Date()
114 if let expiryString = claims?["exp"] as? String {
115 let formatter = ISO8601DateFormatter()
116 let expiryDate = formatter.date(from: expiryString)!
117 if expiryDate < now - maxTokenAge {
118 throw ExpoAtprotoAuthError.invalidPayload("token expired")
119 }
120 } else {
121 throw ExpoAtprotoAuthError.invalidPayload("expiry missing")
122 }
123 }
124
125 if let issuer = options.issuer {
126 if claims?["iss"] as? String != issuer {
127 throw ExpoAtprotoAuthError.invalidPayload("issuer mismatch")
128 }
129 }
130
131 let res = VerifyResponse()
132 res.payload = payload
133 res.protectedHeader = protectedHeader
134
135 return res
136 }
137}