diff --git a/Jot.xcodeproj/project.pbxproj b/Jot.xcodeproj/project.pbxproj index bf411ca..417dc61 100644 --- a/Jot.xcodeproj/project.pbxproj +++ b/Jot.xcodeproj/project.pbxproj @@ -7,18 +7,15 @@ objects = { /* Begin PBXBuildFile section */ + 821E160721D7ED3F00E2D71A /* TokenDeserializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821E160621D7ED3F00E2D71A /* TokenDeserializer.swift */; }; 82CAF46A21D7D41800FED241 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAF46921D7D41800FED241 /* AppDelegate.swift */; }; 82CAF46C21D7D41C00FED241 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 82CAF46B21D7D41C00FED241 /* Assets.xcassets */; }; 82CAF46F21D7D41C00FED241 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 82CAF46D21D7D41C00FED241 /* MainMenu.xib */; }; 82CAF47B21D7D41C00FED241 /* JotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAF47A21D7D41C00FED241 /* JotTests.swift */; }; 82CAF48821D7D56D00FED241 /* JWT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAF48721D7D56D00FED241 /* JWT.swift */; }; - 82CAF48921D7D57700FED241 /* JWT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAF48721D7D56D00FED241 /* JWT.swift */; }; 82CAF48B21D7D5B700FED241 /* Payload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAF48A21D7D5B700FED241 /* Payload.swift */; }; 82CAF48D21D7D60100FED241 /* Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAF48C21D7D60000FED241 /* Header.swift */; }; 82CAF48F21D7D81800FED241 /* Signature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAF48E21D7D81800FED241 /* Signature.swift */; }; - 82CAF49021D7DC5100FED241 /* Payload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAF48A21D7D5B700FED241 /* Payload.swift */; }; - 82CAF49121D7DC5300FED241 /* Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAF48C21D7D60000FED241 /* Header.swift */; }; - 82CAF49221D7DC5600FED241 /* Signature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAF48E21D7D81800FED241 /* Signature.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -32,6 +29,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 821E160621D7ED3F00E2D71A /* TokenDeserializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDeserializer.swift; sourceTree = ""; }; 82CAF46621D7D41800FED241 /* Jot.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Jot.app; sourceTree = BUILT_PRODUCTS_DIR; }; 82CAF46921D7D41800FED241 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 82CAF46B21D7D41C00FED241 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -113,6 +111,7 @@ 82CAF48A21D7D5B700FED241 /* Payload.swift */, 82CAF48C21D7D60000FED241 /* Header.swift */, 82CAF48E21D7D81800FED241 /* Signature.swift */, + 821E160621D7ED3F00E2D71A /* TokenDeserializer.swift */, ); path = Token; sourceTree = ""; @@ -224,6 +223,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 821E160721D7ED3F00E2D71A /* TokenDeserializer.swift in Sources */, 82CAF48D21D7D60100FED241 /* Header.swift in Sources */, 82CAF48B21D7D5B700FED241 /* Payload.swift in Sources */, 82CAF46A21D7D41800FED241 /* AppDelegate.swift in Sources */, @@ -236,11 +236,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 82CAF49121D7DC5300FED241 /* Header.swift in Sources */, - 82CAF49021D7DC5100FED241 /* Payload.swift in Sources */, 82CAF47B21D7D41C00FED241 /* JotTests.swift in Sources */, - 82CAF49221D7DC5600FED241 /* Signature.swift in Sources */, - 82CAF48921D7D57700FED241 /* JWT.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Jot/Token/Header.swift b/Jot/Token/Header.swift index 8e6b381..859a830 100644 --- a/Jot/Token/Header.swift +++ b/Jot/Token/Header.swift @@ -23,7 +23,9 @@ enum JwtType: String, Codable { } enum SigningAlgorithm: String, Codable { - case rsa = "RSA" - case hmac = "HMAC" - case ecdsa = "ECDSA" + case rsa256 = "RS256" + case rsa384 = "RS384" + case rsa512 = "RS512" + case hmac256 = "HM256" + case ecdsa256 = "ES256" } diff --git a/Jot/Token/JWT.swift b/Jot/Token/JWT.swift index 774ed00..6f49c9b 100644 --- a/Jot/Token/JWT.swift +++ b/Jot/Token/JWT.swift @@ -9,19 +9,13 @@ import Foundation class JWT { - private let header: Header - private let payload: Payload - private let signature: Signature + let header: Header + let payload: Payload + let signature: Signature init(header: Header, payload: Payload, signature: Signature) { self.header = header self.payload = payload self.signature = signature } - - init?(token: String) { - return nil - } } - - diff --git a/Jot/Token/TokenDeserializer.swift b/Jot/Token/TokenDeserializer.swift new file mode 100644 index 0000000..e0b63e9 --- /dev/null +++ b/Jot/Token/TokenDeserializer.swift @@ -0,0 +1,62 @@ +// +// TokenDeserializer.swift +// Jot +// +// Created by James Griffin-Allwood on 2018-12-29. +// Copyright © 2018 James Griffin-Allwood. All rights reserved. +// + +import Foundation +import os.log + +extension JWT { + convenience init?(token: String) { + let decoder = JSONDecoder() + let jwtParts = token.split(separator: ".") + guard jwtParts.count == 3 else { + os_log("Invalid JWT format. Requires 3 parts", log: OSLog.default, type: .error) + return nil + } + + let base64Header = String(jwtParts[0]) + guard let headerData = Data(base64Encoded: base64Header.base64Pad()) else { + os_log("Header must be Base64 Encoded", log: OSLog.default, type: .error) + return nil + } + + + let base64Payload = String(jwtParts[1]) + guard let payloadData = Data(base64Encoded: base64Payload.base64Pad()) else { + os_log("Payload must be Base64 Encoded", log: OSLog.default, type: .error) + return nil + } + + // TODO: Base64 Decoding the signature fails. Leave alone until adding Token Validation + // let base64Signature = String(jwtParts[2]) + // guard let signatureData = Data(base64Encoded: base64Signature.base64Pad()) else { + // os_log("Signature must be Base64 Encoded", log: OSLog.default, type: .error) + // return nil + // } + + do { + let header = try decoder.decode(Header.self, from: headerData) + let payload = try decoder.decode(Payload.self, from: payloadData) + let signature = Signature() + self.init(header: header, payload: payload, signature: signature) + } catch { + os_log("Unable to deserialize JWT: %@", log: OSLog.default, type: .error, error.localizedDescription) + return nil + } + } +} + +extension String { + func base64Pad() -> String { + var paddedString = self + if self.count % 4 != 0 { + let charactersToAdd = 4 - self.count % 4 + paddedString.append(contentsOf: repeatElement("=", count: charactersToAdd)) + } + return paddedString + } +} diff --git a/JotTests/JotTests.swift b/JotTests/JotTests.swift index 2c7e4d5..bde3715 100644 --- a/JotTests/JotTests.swift +++ b/JotTests/JotTests.swift @@ -10,13 +10,15 @@ import XCTest @testable import Jot class JotTests: XCTestCase { - var jwt: JWT? - - override func setUp() { - self.jwt = JWT(token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqZ3JpZmZpbiIsIm5hbWUiOiJKYW1lcyBHcmlmZmluIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.DXfNW7Gzvie7LIl5-HSG9clm3sjeglYG2uNmsPvcNerBQbzLI06vys4YqqCMtrWKEFij_y2hKGP9YFdHSRMdtyVcGEXmYP6H6E62uGhASmGFzEHeSa6B1X1-v8lRt_YpIGEXhkUnxfhdcQ90HJlNfajv0ndnRFk8YJxRkmr99OI") - } func testJWTInit() { - XCTAssertNotNil(self.jwt) + guard let jwt = JWT(token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqZ3JpZmZpbiIsIm5hbWUiOiJKYW1lcyBHcmlmZmluIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.DXfNW7Gzvie7LIl5-HSG9clm3sjeglYG2uNmsPvcNerBQbzLI06vys4YqqCMtrWKEFij_y2hKGP9YFdHSRMdtyVcGEXmYP6H6E62uGhASmGFzEHeSa6B1X1-v8lRt_YpIGEXhkUnxfhdcQ90HJlNfajv0ndnRFk8YJxRkmr99OI") else { + XCTFail() + return + } + + XCTAssertEqual(jwt.header.alg, SigningAlgorithm.rsa256) + XCTAssertEqual(jwt.header.typ, JwtType.jwt) } + }