Skip to content

Commit 5dc3ead

Browse files
committed
Only verify with known algorithms
1 parent b26e118 commit 5dc3ead

File tree

3 files changed

+50
-38
lines changed

3 files changed

+50
-38
lines changed

JWT/Decode.swift

+14-16
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ public enum InvalidToken : Printable {
99
/// The JWT uses an unsupported algorithm
1010
case InvalidAlgorithm
1111

12-
/// An invalid key was used when trying to validate a JWT
13-
case InvalidKey
14-
1512
/// The issued claim has expired
1613
case ExpiredSignature
1714

@@ -43,9 +40,7 @@ public enum InvalidToken : Printable {
4340
case InvalidAudience:
4441
return "Invalid Audience"
4542
case InvalidAlgorithm:
46-
return "Unsupported Algorithm"
47-
case InvalidKey:
48-
return "Invalid Key"
43+
return "Unsupported algorithm or incorrect key"
4944
}
5045
}
5146
}
@@ -60,13 +55,12 @@ public enum DecodeResult {
6055
case Failure(InvalidToken)
6156
}
6257

63-
6458
/// Decode a JWT
65-
public func decode(jwt:String, key:String? = nil, verify:Bool = true, audience:String? = nil, issuer:String? = nil) -> DecodeResult {
59+
public func decode(jwt:String, algorithms:[Algorithm], verify:Bool = true, audience:String? = nil, issuer:String? = nil) -> DecodeResult {
6660
switch load(jwt) {
6761
case let .Success(header, payload, signature, signatureInput):
6862
if verify {
69-
if let failure = validateClaims(payload, audience, issuer) ?? verifySignature(header, signatureInput, signature, key) {
63+
if let failure = validateClaims(payload, audience, issuer) ?? verifySignature(algorithms, header, signatureInput, signature) {
7064
return .Failure(failure)
7165
}
7266
}
@@ -77,6 +71,11 @@ public func decode(jwt:String, key:String? = nil, verify:Bool = true, audience:S
7771
}
7872
}
7973

74+
/// Decode a JWT
75+
public func decode(jwt:String, algorithm:Algorithm, verify:Bool = true, audience:String? = nil, issuer:String? = nil) -> DecodeResult {
76+
return decode(jwt, [algorithm], verify: verify, audience: audience, issuer: issuer)
77+
}
78+
8079
// MARK: Parsing a JWT
8180

8281
enum LoadResult {
@@ -125,14 +124,13 @@ func load(jwt:String) -> LoadResult {
125124

126125
// MARK: Signature Verification
127126

128-
func verifySignature(header:Payload, signingInput:String, signature:NSData, key:String?) -> InvalidToken? {
127+
func verifySignature(algorithms:[Algorithm], header:Payload, signingInput:String, signature:NSData) -> InvalidToken? {
129128
if let alg = header["alg"] as? String {
130-
if let algoritm = Algorithm.algorithm(alg, key: key) {
131-
if algoritm.verify(signingInput, signature: signature) {
132-
return nil
133-
} else {
134-
return .InvalidKey
135-
}
129+
let matchingAlgorithms = filter(algorithms) { algorithm in algorithm.description == alg }
130+
let results = map(matchingAlgorithms) { algorithm in algorithm.verify(signingInput, signature: signature) }
131+
let successes = filter(results) { $0 }
132+
if successes.count > 0 {
133+
return nil
136134
}
137135

138136
return .InvalidAlgorithm

JWTTests/JWTTests.swift

+27-21
Original file line numberDiff line numberDiff line change
@@ -13,57 +13,58 @@ class JWTEncodeTests : XCTestCase {
1313
class JWTDecodeTests : XCTestCase {
1414
func testDecodingValidJWT() {
1515
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiS3lsZSJ9.zxm7xcp1eZtZhp4t-nlw09ATQnnFKIiSN83uG8u6cAg"
16-
assertSuccess(decode(jwt, key:"secret")) { payload in
16+
let result = JWT.decode(jwt, .HS256("secret"))
17+
assertSuccess(result) { payload in
1718
XCTAssertEqual(payload as NSDictionary, ["name": "Kyle"])
1819
}
1920
}
2021

2122
func testFailsToDecodeInvalidStringWithoutThreeSegments() {
22-
assertDecodeError(decode("a.b"), "Not enough segments")
23+
assertDecodeError(decode("a.b", .None), "Not enough segments")
2324
}
2425

2526
// MARK: Disable verify
2627

2728
func testDisablingVerify() {
2829
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w"
29-
assertSuccess(decode(jwt, verify:false, issuer:"fuller.li"))
30+
assertSuccess(decode(jwt, .None, verify:false, issuer:"fuller.li"))
3031
}
3132

3233
// MARK: Issuer claim
3334

3435
func testSuccessfulIssuerValidation() {
3536
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmdWxsZXIubGkifQ.d7B7PAQcz1E6oNhrlxmHxHXHgg39_k7X7wWeahl8kSQ"
36-
assertSuccess(decode(jwt, key:"secret", issuer:"fuller.li")) { payload in
37+
assertSuccess(decode(jwt, .HS256("secret"), issuer:"fuller.li")) { payload in
3738
XCTAssertEqual(payload as NSDictionary, ["iss": "fuller.li"])
3839
}
3940
}
4041

4142
func testIncorrectIssuerValidation() {
4243
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmdWxsZXIubGkifQ.wOhJ9_6lx-3JGJPmJmtFCDI3kt7uMAMmhHIslti7ryI"
43-
assertFailure(decode(jwt, key:"secret", issuer:"querykit.org"))
44+
assertFailure(decode(jwt, .HS256("secret"), issuer:"querykit.org"))
4445
}
4546

4647
func testMissingIssuerValidation() {
4748
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w"
48-
assertFailure(decode(jwt, key:"secret", issuer:"fuller.li"))
49+
assertFailure(decode(jwt, .HS256("secret"), issuer:"fuller.li"))
4950
}
5051

5152
// MARK: Expiration claim
5253

5354
func testExpiredClaim() {
5455
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0MjgxODg0OTF9.cy6b2szsNkKnHFnz2GjTatGjoHBTs8vBKnPGZgpp91I"
55-
assertFailure(decode(jwt, key:"secret"))
56+
assertFailure(decode(jwt, .HS256("secret")))
5657
}
5758

5859
func testInvalidExpiaryClaim() {
5960
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOlsiMTQyODE4ODQ5MSJdfQ.OwF-wd3THjxrEGUhh6IdnNhxQZ7ydwJ3Z6J_dfl9MBs"
60-
assertFailure(decode(jwt, key:"secret"))
61+
assertFailure(decode(jwt, .HS256("secret")))
6162
}
6263

6364
func testUnexpiredClaim() {
6465
// If this just started failing, hello 2024!
6566
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjgxODg0OTF9.EW7k-8Mvnv0GpvOKJalFRLoCB3a3xGG3i7hAZZXNAz0"
66-
assertSuccess(decode(jwt, key:"secret")) { payload in
67+
assertSuccess(decode(jwt, .HS256("secret"))) { payload in
6768
XCTAssertEqual(payload as NSDictionary, ["exp": 1728188491])
6869
}
6970
}
@@ -72,81 +73,86 @@ class JWTDecodeTests : XCTestCase {
7273

7374
func testNotBeforeClaim() {
7475
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0MjgxODk3MjB9.jFT0nXAJvEwyG6R7CMJlzNJb7FtZGv30QRZpYam5cvs"
75-
assertSuccess(decode(jwt, key:"secret")) { payload in
76+
assertSuccess(decode(jwt, .HS256("secret"))) { payload in
7677
XCTAssertEqual(payload as NSDictionary, ["nbf": 1428189720])
7778
}
7879
}
7980

8081
func testInvalidNotBeforeClaim() {
8182
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOlsxNDI4MTg5NzIwXX0.PUL1FQubzzJa4MNXe2D3d5t5cMaqFr3kYlzRUzly-C8"
82-
assertDecodeError(decode(jwt, key:"secret"), "Not before claim (nbf) must be an integer")
83+
assertDecodeError(decode(jwt, .HS256("secret")), "Not before claim (nbf) must be an integer")
8384
}
8485

8586
func testUnmetNotBeforeClaim() {
8687
// If this just started failing, hello 2024!
8788
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3MjgxODg0OTF9.Tzhu1tu-7BXcF5YEIFFE1Vmg4tEybUnaz58FR4PcblQ"
88-
assertFailure(decode(jwt, key:"secret"))
89+
assertFailure(decode(jwt, .HS256("secret")))
8990
}
9091

9192
// MARK: Issued at claim
9293

9394
func testIssuedAtClaimInThePast() {
9495
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MjgxODk3MjB9.I_5qjRcCUZVQdABLwG82CSuu2relSdIyJOyvXWUAJh4"
95-
assertSuccess(decode(jwt, key:"secret")) { payload in
96+
assertSuccess(decode(jwt, .HS256("secret"))) { payload in
9697
XCTAssertEqual(payload as NSDictionary, ["iat": 1428189720])
9798
}
9899
}
99100

100101
func testIssuedAtClaimInTheFuture() {
101102
// If this just started failing, hello 2024!
102103
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MjgxODg0OTF9.owHiJyJmTcW1lBW5y_Rz3iBfSbcNiXlbZ2fY9qR7-aU"
103-
assertFailure(decode(jwt, key:"secret"))
104+
assertFailure(decode(jwt, .HS256("secret")))
104105
}
105106

106107
func testInvalidIssuedAtClaim() {
107108
// If this just started failing, hello 2024!
108109
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOlsxNzI4MTg4NDkxXX0.ND7QMWtLkXDXH38OaXM3SQgLo3Z5TNgF_pcfWHV_alQ"
109-
assertDecodeError(decode(jwt, key:"secret"), "Issued at claim (iat) must be an integer")
110+
assertDecodeError(decode(jwt, .HS256("secret")), "Issued at claim (iat) must be an integer")
110111
}
111112

112113
// MARK: Audience claims
113114

114115
func testAudiencesClaim() {
115116
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsibWF4aW5lIiwia2F0aWUiXX0.-PKvdNLCClrWG7CvesHP6PB0-vxu-_IZcsYhJxBy5JM"
116-
assertSuccess(decode(jwt, key:"secret", audience:"maxine")) { payload in
117+
assertSuccess(decode(jwt, .HS256("secret"), audience:"maxine")) { payload in
117118
XCTAssertEqual(payload as NSDictionary, ["aud": ["maxine", "katie"]])
118119
}
119120
}
120121

121122
func testAudienceClaim() {
122123
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJreWxlIn0.dpgH4JOwueReaBoanLSxsGTc7AjKUvo7_M1sAfy_xVE"
123-
assertSuccess(decode(jwt, key:"secret", audience:"kyle")) { payload in
124+
assertSuccess(decode(jwt, .HS256("secret"), audience:"kyle")) { payload in
124125
XCTAssertEqual(payload as NSDictionary, ["aud": "kyle"])
125126
}
126127
}
127128

128129
func testMismatchAudienceClaim() {
129130
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJreWxlIn0.VEB_n06pTSLlTXPFkc46ARADJ9HXNUBUPo3VhL9RDe4" // kyle
130-
assertFailure(decode(jwt, key:"secret", audience:"maxine"))
131+
assertFailure(decode(jwt, .HS256("secret"), audience:"maxine"))
131132
}
132133

133134
func testMissingAudienceClaim() {
134135
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w"
135-
assertFailure(decode(jwt, key:"secret", audience:"kyle"))
136+
assertFailure(decode(jwt, .HS256("secret"), audience:"kyle"))
136137
}
137138

138139
// MARK: Signature verification
139140

140141
func testNoneAlgorithm() {
141142
let jwt = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ0ZXN0IjoiaW5nIn0."
142-
assertSuccess(decode(jwt)) { payload in
143+
assertSuccess(decode(jwt, .None)) { payload in
143144
XCTAssertEqual(payload as NSDictionary, ["test": "ing"])
144145
}
145146
}
146147

147148
func testNoneFailsWithSecretAlgorithm() {
148149
let jwt = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ0ZXN0IjoiaW5nIn0."
149-
assertFailure(decode(jwt, key:"secret"))
150+
assertFailure(decode(jwt, .HS256("secret")))
151+
}
152+
153+
func testMatchesAnyAlgorithm() {
154+
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w."
155+
assertFailure(decode(jwt, [.HS256("anothersecret"), .HS256("secret")]))
150156
}
151157
}
152158

README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,16 @@ JWT.encode(["my": "payload"], .HS256("secret"))
2626

2727
### Decoding a JWT
2828

29+
When decoding a JWT, you must supply one or more algorithms and keys.
30+
31+
```swift
32+
JWT.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w", .HS256("secret"))
33+
```
34+
35+
When the JWT may be signed with one out of many algorithms or keys:
36+
2937
```swift
30-
JWT.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w")
38+
JWT.decode("eyJh...5w", [.HS256("secret"), .HS256("secret2"), .HS512("secure")])
3139
```
3240

3341
#### Supported claims

0 commit comments

Comments
 (0)