diff --git a/JWT/JWTTests/JWTTests.swift b/JWT/JWTTests/JWTTests.swift index 87d2aa9..42a1268 100644 --- a/JWT/JWTTests/JWTTests.swift +++ b/JWT/JWTTests/JWTTests.swift @@ -54,16 +54,16 @@ class JWTTests: XCTestCase { func test_random_string_as_JWT() { var error: NSError? var jwt = JWT(algorithms: ["HS512","RS512"]) - XCTAssert(jwt.loads("randomstring", key: nil, verify: false, error: &error) == false, "random string should not load") + XCTAssert(jwt.loads("randomstring", verify: false, error: &error) == false, "random string should not load") XCTAssert(jwt.body.count == 0, "loading garbage should leave body empty") } func test_alg_none_with_sig() { let jwt_none_sig = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.qrq-939iZydNFdNsTosbSteghjc2VcK9EZVklxfQgiU" var jwt = JWT(algorithms: ["none","HS512","RS512"]) - XCTAssert(jwt.loads(jwt_none_sig, key: nil, verify: true) == false, "alg=none with a signature is not valid") + XCTAssert(jwt.loads(jwt_none_sig, verify: true) == false, "alg=none with a signature is not valid") jwt = JWT(algorithms: ["HS512","RS512"]) - XCTAssert(jwt.loads(jwt_none_sig, key: nil, verify: false) == false, "none is not on whitelist") + XCTAssert(jwt.loads(jwt_none_sig, verify: false) == false, "none is not on whitelist") } func test_timestamps() { @@ -74,22 +74,22 @@ class JWTTests: XCTestCase { var s = "" jwt_dated.body["exp"] = now-100 s = jwt_dated.dumps()! - XCTAssert(jwt.loads(s, key: nil, verify: true) == false, "exp in past \(s)") + XCTAssert(jwt.loads(s, verify: true) == false, "exp in past \(s)") jwt_dated.body["exp"] = now+100 // and leave it there for next tests s = jwt_dated.dumps()! - XCTAssert(jwt.loads(s, key: nil, verify: true) == true, "exp in future \(s)") + XCTAssert(jwt.loads(s, verify: true) == true, "exp in future \(s)") jwt_dated.body["nbf"] = now+100 s = jwt_dated.dumps()! - XCTAssert(jwt.loads(s, key: nil, verify: true) == false, "nbf in future \(s)") + XCTAssert(jwt.loads(s, verify: true) == false, "nbf in future \(s)") jwt_dated.body["nbf"] = now-100 // and leave it there for next tests s = jwt_dated.dumps()! - XCTAssert(jwt.loads(s, key: nil, verify: true) == true, "nbf in past \(s)") + XCTAssert(jwt.loads(s, verify: true) == true, "nbf in past \(s)") jwt_dated.body["iat"] = now+100 s = jwt_dated.dumps()! - XCTAssert(jwt.loads(s, key: nil, verify: true) == false, "iat in future \(s)") + XCTAssert(jwt.loads(s, verify: true) == false, "iat in future \(s)") jwt_dated.body["iat"] = now-100 // and leave it there for next tests s = jwt_dated.dumps()! - XCTAssert(jwt.loads(s, key: nil, verify: true) == true, "iat in past \(s)") + XCTAssert(jwt.loads(s, verify: true) == true, "iat in past \(s)") } func test_NaCl_JWT_load_dump_load() { @@ -103,12 +103,10 @@ class JWTTests: XCTestCase { XCTAssert(kp != nil, "Key pair generation") 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?) + // but is still loaded (DO WE WANT THAT?) NOW FAILS + jwt = JWTNaCl(header: ["alg":"Ed25519","kid":"XN7VpEX1uCxxhvwUuacYhuU9t6uxgLahRiLeSEHENik"], body: ["hello":"world"], algorithms: ["Ed25519"]) let jwt_str = jwt.dumps(key: kp!.secretKey)! // valid Ed25519 signed token - jwt.header = [:] - 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, verify: true) == false, "verify a generated JWT with wrong kid when signed with fresh key") XCTAssert(jwt.loads(jwt_str, key: kp!.publicKey, verify: true), "verify a generated JWT with its public key") } @@ -117,7 +115,7 @@ class JWTTests: XCTestCase { let jwt_str = "eyJhbGciOiJFZDI1NTE5IiwidHlwIjoiSldUIiwia2lkIjoiYUJHb3dQSGNJdHBvdmVWenJyUXNTbms2NWNfcWhLdmZqZC00d3lQVWZVUSJ9.eyJwaG9uZV9udW1iZXIiOiIrMzA2OTQ3ODk4NjA1Iiwic2NvcGUiOiJwaG9uZSIsImF1ZCI6Imh0dHBzOlwvXC81LWRvdC1hdXRoZW50aXFpby5hcHBzcG90LmNvbSIsInN1YiI6ImFCR293UEhjSXRwb3ZlVnpyclFzU25rNjVjX3FoS3ZmamQtNHd5UFVmVVEiLCJ0eXBlIjoibW9iaWxlIn0.kD4YcuAb7v3cxlRZTrUbew1lWiY3G8uEmRguizy1KJs" var jwt = JWTNaCl(algorithms: ["none","HS512","RS512"]) self.measureBlock() { - let ok = jwt.loads(jwt_str, key: nil, verify: true) + let ok = jwt.loads(jwt_str, verify: true) } } diff --git a/SwiftJWT.swift b/SwiftJWT.swift index 9b7a19e..46e316a 100644 --- a/SwiftJWT.swift +++ b/SwiftJWT.swift @@ -43,11 +43,16 @@ public class JWT { public func loads(jwt: String, key: NSData? = nil, verify: Bool = true, error: NSErrorPointer = nil) -> Bool { // load a JWT string into this object + var sig = "" + var hdr: [String: AnyObject]? + var payload: [String: AnyObject]? + var algorithm: String? + + // clear object properties self.header = [:] self.body = [:] - var sig = "" - // split into parts, header, body, optional signature + // split JWT string into parts: header, body, optional signature let parts: [String] = jwt.componentsSeparatedByString(".") switch parts.count { case 2: break @@ -61,45 +66,48 @@ 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 - let alg = self.header["alg"] as? String - if !self.implemented(alg) { + hdr = NSJSONSerialization.JSONObjectWithData(hdr_data, options: NSJSONReadingOptions(0), error: error) as? [String: AnyObject] + if hdr != nil { + // check that "alg" header is on whitelist (and thus implemented) ; even if verify == false + algorithm = hdr!["alg"] as? String + if !self.whitelisted(algorithm) { return false // TODO: populate NSError } } else { return false // TODO: populate NSError } - // 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 - } // decode the body (a URL-safe base 64 encoded JSON dict) from the 2nd part if parts.count > 1 { let body_data = parts[1].base64SafeUrlDecode() - if let dictionary = NSJSONSerialization.JSONObjectWithData(body_data, options: NSJSONReadingOptions(0), error: error) as? [String: AnyObject] { - self.body = dictionary + payload = NSJSONSerialization.JSONObjectWithData(body_data, options: NSJSONReadingOptions(0), error: error) as? [String: AnyObject] + if payload == nil { + return false // TODO: populate NSError } } else { return false // TODO: populate NSError } + // all went well so far, so let's set the object properties + // TODO: set properties even later (but are needed by verification methods now) + self.header = hdr! + self.body = payload! + if verify { // verify the signature, a URL-safe base64 encoded string let hdr_body: String = parts[0] + "." + parts[1] // header & body of a JWT let data = hdr_body.dataUsingEncoding(NSUTF8StringEncoding)! if self.verify_signature(data, signature: sig, algorithm: algorithm!, key: key) == false { + self.header = [:]; self.body = [:] // reset return false // TODO: populate NSError } // verify content fields if self.verify_content() == false { - return false + self.header = [:]; self.body = [:] // reset + return false // TODO: populate NSError } } - // TODO: do not load header & body if verification fails? return true } @@ -162,6 +170,7 @@ public class JWT { func implemented(algorithm: String?) -> Bool { let algorithms = ["none", "HS256", "HS384", "HS512"] + // TODO: add RS256, RS384, RS512 when rsa_* methods below are done for alg in algorithms { if alg == algorithm { return true @@ -445,15 +454,16 @@ extension NSData { let privkey: SecKey? = nil let msg = UnsafePointer(self.bytes) let msglen = UInt(self.length) + let digestLen = algorithm.digestLength() - let sha_buf = UnsafeMutablePointer.alloc(Int(CC_SHA256_DIGEST_LENGTH)) // or 224 or 384 or 512 - let sha_result = CC_SHA256(msg, CC_LONG(msglen), sha_buf) // or 224 or 384 or 512 + let sha_buf = UnsafeMutablePointer.alloc(Int(digestLen)) + let sha_result = CC_SHA256(msg, CC_LONG(msglen), sha_buf) // TODO: use 384 or 512 versions, depending on algorithm - var sig = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH))! // or 224 or 384 or 512 - var sigbuf = UnsafeMutablePointer(sig.mutableBytes) // or UnsafeMutablePointer.alloc(Int(CC_SHA256_DIGEST_LENGTH)) ? - let siglen = UnsafeMutablePointer.alloc(1) // correct? and initialize to CC_SHA256_DIGEST_LENGTH ? + var sig = NSMutableData(length: Int(digestLen))! + var sigbuf = UnsafeMutablePointer(sig.mutableBytes) // or UnsafeMutablePointer.alloc(Int(digestLen)) ? + let siglen = UnsafeMutablePointer.alloc(1) // correct? and initialize to digestLen ? - // TODO: fix error in next line, call to SecKeyRawSign + // TODO: fix error in next line, call to SecKeyRawSign, and use right padding constant //let status = SecKeyRawSign(key: privkey!, padding: kSecPaddingPKCS1SHA256, dataToSign: msg, dataToSignLen: msglen, sig: sigbuf, sigLen: siglen) //OSStatus SecKeyRawSign( @@ -471,16 +481,17 @@ extension NSData { let pubkey: SecKey? = nil let msg = UnsafePointer(self.bytes) let msglen = UInt(self.length) + let digestLen = algorithm.digestLength() - let sha_buf = UnsafeMutablePointer.alloc(Int(CC_SHA256_DIGEST_LENGTH)) - let sha_result = CC_SHA256(msg, CC_LONG(msglen), sha_buf) + let sha_buf = UnsafeMutablePointer.alloc(Int(digestLen)) + let sha_result = CC_SHA256(msg, CC_LONG(msglen), sha_buf) // TODO: use 384 or 512 versions, depending on algorithm - var sig = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH))! + var sig = NSMutableData(length: Int(digestLen))! let sig_raw = signature.base64SafeUrlDecode() - var sigbuf = UnsafeMutablePointer(sig_raw.bytes) // or UnsafeMutablePointer.alloc(Int(CC_SHA256_DIGEST_LENGTH)) ? + var sigbuf = UnsafeMutablePointer(sig_raw.bytes) // or UnsafeMutablePointer.alloc(Int(digestLen)) ? let siglen = UInt(sig_raw.length) - // TODO: fix error in next line, call to SecKeyRawSign + // TODO: fix error in next line, call to SecKeyRawVerify, and use right padding constant // let status = SecKeyRawVerify(key: pubkey!, padding: kSecPaddingPKCS1SHA256, signedData: msg, signedDataLen: msglen, sig: sigbuf, sigLen: siglen) // OSStatus SecKeyRawVerify(key: SecKey!, padding: SecPadding, signedData: UnsafePointer, signedDataLen: UInt, sig: UnsafePointer, sigLen: UInt)