diff --git a/JWT/JWTTests/JWTTests.swift b/JWT/JWTTests/JWTTests.swift index 1ec3336..87d2aa9 100644 --- a/JWT/JWTTests/JWTTests.swift +++ b/JWT/JWTTests/JWTTests.swift @@ -25,7 +25,7 @@ class JWTTests: XCTestCase { func test_HS256_JWT_from_dicts() { // This is an example of a functional test case. let expected_jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJoZWxsbyI6IndvcmxkIn0.lnneNaoem98xYFES3mi2CJJjnMONuWAu-FTWB3XJN14" - let jwt = JWT(header: ["alg":"HS256"], body: ["hello":"world"], algorithms: ["HS512","RS512"]) + let jwt = JWT(header: ["alg":"HS256"], body: ["hello":"world"], algorithms: ["HS256","HS512","RS512"]) let s = jwt.dumps("secret", jti_len: 0) // without jti to enable replay XCTAssert(s != nil, "JWT.dumps() failed") XCTAssert(s! == expected_jwt, "JWT.dumps() unexpected jwt") @@ -73,22 +73,22 @@ class JWTTests: XCTestCase { var jwt_dated = JWT(algorithms: ["none","HS512","RS512"]) var s = "" jwt_dated.body["exp"] = now-100 - s = jwt_dated.dumps("")! + s = jwt_dated.dumps()! XCTAssert(jwt.loads(s, key: nil, verify: true) == false, "exp in past \(s)") jwt_dated.body["exp"] = now+100 // and leave it there for next tests - s = jwt_dated.dumps("")! + s = jwt_dated.dumps()! XCTAssert(jwt.loads(s, key: nil, verify: true) == true, "exp in future \(s)") jwt_dated.body["nbf"] = now+100 - s = jwt_dated.dumps("")! + s = jwt_dated.dumps()! XCTAssert(jwt.loads(s, key: nil, verify: true) == false, "nbf in future \(s)") jwt_dated.body["nbf"] = now-100 // and leave it there for next tests - s = jwt_dated.dumps("")! + s = jwt_dated.dumps()! XCTAssert(jwt.loads(s, key: nil, verify: true) == true, "nbf in past \(s)") jwt_dated.body["iat"] = now+100 - s = jwt_dated.dumps("")! + s = jwt_dated.dumps()! XCTAssert(jwt.loads(s, key: nil, verify: true) == false, "iat in future \(s)") jwt_dated.body["iat"] = now-100 // and leave it there for next tests - s = jwt_dated.dumps("")! + s = jwt_dated.dumps()! XCTAssert(jwt.loads(s, key: nil, verify: true) == true, "iat in past \(s)") } @@ -104,9 +104,9 @@ class JWTTests: XCTestCase { var jwt = JWTNaCl(algorithms: ["Ed25519"]) XCTAssert(jwt.loads(jwt_ed, key: kp!.publicKey, verify: true) == false, "NaCl JWT should not validate with wrong key") // but is still loaded (DO WE WANT THAT?) - let jwt_str = jwt.dumps(kp!.secretKey)! // valid Ed25519 signed token + let jwt_str = jwt.dumps(key: kp!.secretKey)! // valid Ed25519 signed token jwt.header = [:] - XCTAssert(jwt.header["alg"] as? String == "Ed25519", "after reset of header, alg should be unchanged") + XCTAssert(jwt.header["alg"] as? String == "none", "after reset of header, alg should be none") jwt.body = [:] XCTAssert(jwt.loads(jwt_str, key: nil, verify: true) == false, "verify a generated JWT to kid when signed with fresh key") XCTAssert(jwt.loads(jwt_str, key: kp!.publicKey, verify: true), "verify a generated JWT with its public key") diff --git a/SwiftJWT.swift b/SwiftJWT.swift index fb0c8b7..8c140d2 100644 --- a/SwiftJWT.swift +++ b/SwiftJWT.swift @@ -1,5 +1,5 @@ // -// JWT.swift +// SwiftJWT.swift // // Stan P. van de Burgt // stan@vandeburgt.com @@ -20,25 +20,21 @@ public class JWT { if header["alg"] as? String == nil { self.header["alg"] = "none" // if not present, insert alg } - if !self.whitelisted(header["alg"] as? String) { - // TODO: what to do when the alg is not whitelisted? We need Exceptions! - self.header["alg"] = oldValue["alg"] // TODO: or better: fail on loads? - } } } public var body: [String: AnyObject] = [:] // JWT payload var algorithms: [String] = [] // algorithms that are valid on loads(), dumps() and setting 'alg' header public init(algorithms: [String]) { - self.algorithms = algorithms // TODO: check if algoritms are implemented() + self.algorithms = implemented(algorithms) // only add algoritms that are implemented() } public init(header: [String: AnyObject], body: [String: AnyObject], algorithms: [String]) { self.header = header self.body = body - self.algorithms = algorithms // TODO: check if algoritms are implemented() + self.algorithms = implemented(algorithms) // only add algoritms that are implemented() if header["alg"] as? String == nil { - self.header["alg"] = "none" // if not present, insert 'alg' (copy of didSet{} ad init does not trigger it) + self.header["alg"] = "none" // if not present, insert 'alg' } if header["typ"] as? String == nil { self.header["typ"] = "JWT" // if not present, insert 'typ' element @@ -66,12 +62,16 @@ public class JWT { // decode the header (a URL-safe, base 64 encoded JSON dict) from 1st part let hdr_data = parts[0].base64SafeUrlDecode() if let dictionary = NSJSONSerialization.JSONObjectWithData(hdr_data, options: NSJSONReadingOptions(0), error: error) as? [String: AnyObject] { - self.header = dictionary // also sets self.alg and value for "alg" key by the didSet{} observer + self.header = dictionary + let alg = self.header["alg"] as? String + if !self.implemented(alg) { + return false // TODO: populate NSError + } } else { return false // TODO: populate NSError } - // check whether alg parameter is on algorithms whitelist + // check that "alg" header is on whitelist (and thus implemented) ; even if verify == false let algorithm = header["alg"] as? String if !self.whitelisted(algorithm) { return false // TODO: populate NSError @@ -115,10 +115,9 @@ public class JWT { return loads(jwt, key: key_raw, verify: verify, error: error) } - public func dumps(key: NSData, jti_len: UInt = 16, error: NSErrorPointer = nil) -> String? { + public func dumps(key: NSData? = nil, jti_len: UInt = 16, error: NSErrorPointer = nil) -> String? { // create a JWT string from this object // TODO: some way to indicate that some fields should be generated, next to jti; e.g. nbf and iat - var data = "" var payload = self.body // if 'jti' (the nonce) not present in body, and it is requested (jti_len > 0), set one if payload["jti"] as? String == nil && jti_len > 0 { @@ -128,15 +127,18 @@ public class JWT { SecRandomCopyBytes(kSecRandomDefault, jti_len, UnsafeMutablePointer(bytes.mutableBytes)) payload["jti"] = bytes.base64SafeUrlEncode() } - // TODO: set iat, nbf here if not set? + // TODO: set iat, nbf in payload here if not set & requested? if let h = NSJSONSerialization.dataWithJSONObject(self.header, options: nil, error: error) { - data = h.base64SafeUrlEncode() + var data = h.base64SafeUrlEncode() if let b = NSJSONSerialization.dataWithJSONObject(payload, options: nil, error: error) { data = data + "." + b.base64SafeUrlEncode() - let data_raw = data.dataUsingEncoding(NSUTF8StringEncoding)! - let algorithm = header["alg"] as? String - if let sig = self.signature(data_raw, algorithm: algorithm!, key: key) { - return data + "." + sig + // check that "alg" header is on whitelist (and thus implemented) + let alg = self.header["alg"] as? String + if self.whitelisted(alg) { + let data_raw = data.dataUsingEncoding(NSUTF8StringEncoding)! + if let sig = self.signature(data_raw, algorithm: alg!, key: key) { + return data + "." + sig + } } } } @@ -144,9 +146,12 @@ public class JWT { } // helper function for plain strings as key - public func dumps(key: String, jti_len: UInt = 16, error: NSErrorPointer = nil) -> String? { - let key_raw = key.dataUsingEncoding(NSUTF8StringEncoding)! - return dumps(key_raw, jti_len: jti_len, error: error) + public func dumps(key: String?, jti_len: UInt = 16, error: NSErrorPointer = nil) -> String? { + var key_raw: NSData? = nil + if let key_str = key { + key_raw = key_str.dataUsingEncoding(NSUTF8StringEncoding)! + } + return dumps(key: key_raw, jti_len: jti_len, error: error) } func whitelisted(algorithm: String?) -> Bool { @@ -168,18 +173,32 @@ public class JWT { return false } - func signature(msg: NSData, algorithm: String, key: NSData) -> String? { + func implemented(algorithms: [String]) -> [String] { + var result: [String] = [] + for alg in algorithms { + if implemented(alg) { + result.append(alg) + } + } + return result + } + + func signature(msg: NSData, algorithm: String, key: NSData?) -> String? { // internal function to compute the signature (third) part of a JWT - switch algorithm { - case "none": return "" - case "HS256": return msg.base64digest(HMACAlgorithm.SHA256, key: key) - case "HS384": return msg.base64digest(HMACAlgorithm.SHA384, key: key) - case "HS512": return msg.base64digest(HMACAlgorithm.SHA512, key: key) - case "RS256": return msg.rsa_signature(HMACAlgorithm.SHA256, key: key) - case "RS384": return msg.rsa_signature(HMACAlgorithm.SHA384, key: key) - case "RS512": return msg.rsa_signature(HMACAlgorithm.SHA512, key: key) - // TODO: support for RSASSA-PSS algorithms: "PS256", "PS384", and "PS512" - default: return nil + if let key_raw = key { + switch algorithm { + case "HS256": return msg.base64digest(HMACAlgorithm.SHA256, key: key_raw) + case "HS384": return msg.base64digest(HMACAlgorithm.SHA384, key: key_raw) + case "HS512": return msg.base64digest(HMACAlgorithm.SHA512, key: key_raw) + case "RS256": return msg.rsa_signature(HMACAlgorithm.SHA256, key: key_raw) + case "RS384": return msg.rsa_signature(HMACAlgorithm.SHA384, key: key_raw) + case "RS512": return msg.rsa_signature(HMACAlgorithm.SHA512, key: key_raw) + // TODO: support for RSASSA-PSS algorithms: "PS256", "PS384", and "PS512" + default: return nil + } + } + else { + return algorithm == "none" ? "" : nil } } @@ -215,13 +234,13 @@ public class JWT { return false // 'typ' shall be present } if let exp = self.body["exp"] as? UInt { - if now > exp { return false } + if now > exp { return false } // TODO: also false if "exp" is not of type UInt } if let nbf = self.body["nbf"] as? UInt { - if now < nbf { return false } + if now < nbf { return false } // TODO: also false if "nbf" is not of type UInt } if let iat = self.body["iat"] as? UInt { - if now < iat { return false } + if now < iat { return false } // TODO: also false if "iat" is not of type UInt } return true } @@ -243,9 +262,9 @@ public class JWTNaCl: JWT { return super.implemented(algorithm) // not implemented here, so try parent } - override func signature(msg: NSData, algorithm: String, key: NSData) -> String? { + override func signature(msg: NSData, algorithm: String, key: NSData?) -> String? { if algorithm == "Ed25519" { - return msg.nacl_signature(key) + return msg.nacl_signature(key!) // will crash on nil key } else { return super.signature(msg, algorithm: algorithm, key: key)