diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..670eda9 Binary files /dev/null and b/.DS_Store differ diff --git a/JSONWebToken.podspec b/JSONWebToken.podspec index 2bf5306..f628f4e 100644 --- a/JSONWebToken.podspec +++ b/JSONWebToken.podspec @@ -1,17 +1,18 @@ Pod::Spec.new do |spec| spec.name = 'JSONWebToken' - spec.version = '2.1.1' + spec.version = '2.1.2' spec.summary = 'Swift library for JSON Web Tokens (JWT).' spec.homepage = 'https://github.com/kylef/JSONWebToken.swift' spec.license = { :type => 'BSD', :file => 'LICENSE' } spec.author = { 'Kyle Fuller' => 'kyle@fuller.li' } spec.source = { :git => 'https://github.com/kylef/JSONWebToken.swift.git', :tag => "#{spec.version}" } spec.source_files = 'Sources/*.swift' - spec.ios.deployment_target = '8.0' + spec.ios.deployment_target = '8.3' spec.osx.deployment_target = '10.9' - spec.tvos.deployment_target = '9.0' - spec.watchos.deployment_target = '2.0' + spec.tvos.deployment_target = '9.2' + spec.watchos.deployment_target = '2.2' spec.requires_arc = true spec.dependency 'CryptoSwift', '~> 0.6.1' + spec.dependency 'SwiftyRSA', '~> 1.2.0' spec.module_name = 'JWT' end diff --git a/Package.pins b/Package.pins new file mode 100644 index 0000000..4afba9f --- /dev/null +++ b/Package.pins @@ -0,0 +1,12 @@ +{ + "autoPin": true, + "pins": [ + { + "package": "CryptoSwift", + "reason": null, + "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift", + "version": "0.6.9" + } + ], + "version": 1 +} \ No newline at end of file diff --git a/README.md b/README.md index 830f70d..b891baa 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,9 @@ This library supports the following algorithms: - `hs256` - HMAC using SHA-256 hash algorithm (default) - `hs384` - HMAC using SHA-384 hash algorithm - `hs512` - HMAC using SHA-512 hash algorithm +- `rs256` - RSA using SHA-256 hash algorithm +- `rs512` - RSA using SHA-512 hash algorithm + ## License diff --git a/Sources/Decode.swift b/Sources/Decode.swift index ce802d7..8a8b894 100644 --- a/Sources/Decode.swift +++ b/Sources/Decode.swift @@ -5,25 +5,25 @@ import Foundation public enum InvalidToken: CustomStringConvertible, Error { /// Decoding the JWT itself failed case decodeError(String) - + /// The JWT uses an unsupported algorithm case invalidAlgorithm - + /// The issued claim has expired case expiredSignature - + /// The issued claim is for the future case immatureSignature - + /// The claim is for the future case invalidIssuedAt - + /// The audience of the claim doesn't match case invalidAudience - + /// The issuer claim failed to verify case invalidIssuer - + /// Returns a readable description of the error public var description: String { switch self { @@ -49,12 +49,12 @@ public enum InvalidToken: CustomStringConvertible, Error { /// Decode a JWT public func decode(_ jwt: String, algorithms: [Algorithm], verify: Bool = true, audience: String? = nil, issuer: String? = nil) throws -> ClaimSet { let (header, claims, signature, signatureInput) = try load(jwt) - + if verify { try claims.validate(audience: audience, issuer: issuer) try verifySignature(algorithms, header: header, signingInput: signatureInput, signature: signature) } - + return claims } @@ -82,35 +82,35 @@ func load(_ jwt: String) throws -> (header: JOSEHeader, payload: ClaimSet, signa if segments.count != 3 { throw InvalidToken.decodeError("Not enough segments") } - + let headerSegment = segments[0] let payloadSegment = segments[1] let signatureSegment = segments[2] let signatureInput = "\(headerSegment).\(payloadSegment)" - + guard let headerData = base64decode(headerSegment) else { throw InvalidToken.decodeError("Header is not correctly encoded as base64") } - + let header = (try? JSONSerialization.jsonObject(with: headerData, options: JSONSerialization.ReadingOptions(rawValue: 0))) as? Payload if header == nil { throw InvalidToken.decodeError("Invalid header") } - + let payloadData = base64decode(payloadSegment) if payloadData == nil { throw InvalidToken.decodeError("Payload is not correctly encoded as base64") } - + let payload = (try? JSONSerialization.jsonObject(with: payloadData!, options: JSONSerialization.ReadingOptions(rawValue: 0))) as? Payload if payload == nil { throw InvalidToken.decodeError("Invalid payload") } - + guard let signature = base64decode(signatureSegment) else { throw InvalidToken.decodeError("Signature is not correctly encoded as base64") } - + return (header: JOSEHeader(parameters: header!), payload: ClaimSet(claims: payload!), signature: signature, signatureInput: signatureInput) } @@ -120,11 +120,11 @@ func verifySignature(_ algorithms: [Algorithm], header: JOSEHeader, signingInput guard let alg = header.algorithm else { throw InvalidToken.decodeError("Missing Algorithm") } - - let verifiedAlgorithms = algorithms + + let verifiedAlgorithms = try algorithms .filter { algorithm in algorithm.description == alg } - .filter { algorithm in algorithm.verify(signingInput, signature: signature) } - + .filter { algorithm in try algorithm.verify(signingInput, signature: signature) } + if verifiedAlgorithms.isEmpty { throw InvalidToken.invalidAlgorithm } diff --git a/Sources/Encode.swift b/Sources/Encode.swift index fa716d1..24b509b 100644 --- a/Sources/Encode.swift +++ b/Sources/Encode.swift @@ -5,25 +5,25 @@ import Foundation - parameter algorithm: The algorithm to sign the payload with - returns: The JSON web token as a String */ -public func encode(claims: ClaimSet, algorithm: Algorithm, headers: [String: String]? = nil) -> String { +public func encode(claims: ClaimSet, algorithm: Algorithm, headers: [String: String]? = nil) throws -> String { func encodeJSON(_ payload: [String: Any]) -> String? { if let data = try? JSONSerialization.data(withJSONObject: payload) { return base64encode(data) } - + return nil } - + var headers = headers ?? [:] if !headers.keys.contains("typ") { headers["typ"] = "JWT" } headers["alg"] = algorithm.description - + let header = encodeJSON(headers)! let payload = encodeJSON(claims.claims)! let signingInput = "\(header).\(payload)" - let signature = algorithm.sign(signingInput) + let signature = try algorithm.sign(signingInput) return "\(signingInput).\(signature)" } @@ -32,16 +32,16 @@ public func encode(claims: ClaimSet, algorithm: Algorithm, headers: [String: Str - parameter algorithm: The algorithm to sign the payload with - returns: The JSON web token as a String */ -public func encode(claims: [String: Any], algorithm: Algorithm, headers: [String: String]? = nil) -> String { - return encode(claims: ClaimSet(claims: claims), algorithm: algorithm, headers: headers) +public func encode(claims: [String: Any], algorithm: Algorithm, headers: [String: String]? = nil) throws -> String { + return try encode(claims: ClaimSet(claims: claims), algorithm: algorithm, headers: headers) } /// Encode a set of claims using the builder pattern -public func encode(_ algorithm: Algorithm, closure: ((ClaimSetBuilder) -> Void)) -> String { +public func encode(_ algorithm: Algorithm, closure: ((ClaimSetBuilder) -> Void)) throws -> String { let builder = ClaimSetBuilder() closure(builder) - return encode(claims: builder.claims, algorithm: algorithm) + return try encode(claims: builder.claims, algorithm: algorithm) } @@ -51,6 +51,6 @@ public func encode(_ algorithm: Algorithm, closure: ((ClaimSetBuilder) -> Void)) - returns: The JSON web token as a String */ @available(*, deprecated, message: "use encode(claims: algorithm:) instead") -public func encode(_ payload: Payload, algorithm: Algorithm) -> String { - return encode(claims: ClaimSet(claims: payload), algorithm: algorithm) +public func encode(_ payload: Payload, algorithm: Algorithm) throws -> String { + return try encode(claims: ClaimSet(claims: payload), algorithm: algorithm) } diff --git a/Sources/JWT.swift b/Sources/JWT.swift index 3b3e4a3..615777d 100644 --- a/Sources/JWT.swift +++ b/Sources/JWT.swift @@ -1,5 +1,6 @@ import Foundation import CryptoSwift +import SwiftyRSA public typealias Payload = [String: Any] @@ -7,16 +8,22 @@ public typealias Payload = [String: Any] public enum Algorithm: CustomStringConvertible { /// No Algorithm, i-e, insecure case none - + /// HMAC using SHA-256 hash algorithm case hs256(Data) - + /// HMAC using SHA-384 hash algorithm case hs384(Data) - + /// HMAC using SHA-512 hash algorithm case hs512(Data) - + + /// RSA using SHA-256 hash algorithm + case rs256(Data) + + // RSA using SHA-512 hash algorithm + case rs512(Data) + public var description: String { switch self { case .none: @@ -27,13 +34,19 @@ public enum Algorithm: CustomStringConvertible { return "HS384" case .hs512: return "HS512" + case .rs256: + return "RS256" + case .rs512: + return "RS512" } } - + /// Sign a message using the algorithm - func sign(_ message: String) -> String { + func sign(_ message: String) throws -> String { + func signHS(_ key: Data, variant: CryptoSwift.HMAC.Variant) -> String { let messageData = message.data(using: String.Encoding.utf8, allowLossyConversion: false)! + let mac = HMAC(key: key.bytes, variant: variant) let result: [UInt8] do { @@ -43,24 +56,44 @@ public enum Algorithm: CustomStringConvertible { } return base64encode(Data(bytes: result)) } - + + func signRS(_ key: Data, digestType: Signature.DigestType) throws -> String { + + let privateKey = try PrivateKey(data: key) + + let clear = try ClearMessage(string: message, using: .utf8) + + let signature = try clear.signed(with: privateKey, digestType: digestType) + let base64Signature = signature.base64String + + return base64Signature + + } + switch self { case .none: return "" - + case .hs256(let key): return signHS(key, variant: .sha256) - + case .hs384(let key): return signHS(key, variant: .sha384) - + case .hs512(let key): return signHS(key, variant: .sha512) + + case .rs256(let key): + return try signRS(key, digestType: .sha256) + + case .rs512(let key): + return try signRS(key, digestType: .sha512) + } } - + /// Verify a signature for a message using the algorithm - func verify(_ message: String, signature: Data) -> Bool { - return sign(message) == base64encode(signature) + func verify(_ message: String, signature: Data) throws -> Bool { + return try sign(message) == base64encode(signature) } }