Skip to content

Commit

Permalink
check whitelist of algorithms in loads()/dumps()
Browse files Browse the repository at this point in the history
  • Loading branch information
stannie committed Apr 10, 2015
1 parent 09f74ca commit 7984428
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 46 deletions.
18 changes: 9 additions & 9 deletions JWT/JWTTests/JWTTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)")
}

Expand All @@ -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")
Expand Down
93 changes: 56 additions & 37 deletions SwiftJWT.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// JWT.swift
// SwiftJWT.swift
//
// Stan P. van de Burgt
// [email protected]
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -128,25 +127,31 @@ public class JWT {
SecRandomCopyBytes(kSecRandomDefault, jti_len, UnsafeMutablePointer<UInt8>(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
}
}
}
}
return nil // TODO: populate NSError
}

// 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 {
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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
}
Expand All @@ -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)
Expand Down

0 comments on commit 7984428

Please sign in to comment.