Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added RS256 and RS512 Support #77

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
9 changes: 5 additions & 4 deletions JSONWebToken.podspec
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions Package.pins
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"autoPin": true,
"pins": [
{
"package": "CryptoSwift",
"reason": null,
"repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift",
"version": "0.6.9"
}
],
"version": 1
}
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

40 changes: 20 additions & 20 deletions Sources/Decode.swift
Original file line number Diff line number Diff line change
@@ -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
}
22 changes: 11 additions & 11 deletions Sources/Encode.swift
Original file line number Diff line number Diff line change
@@ -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)
}
59 changes: 46 additions & 13 deletions Sources/JWT.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import Foundation
import CryptoSwift
import SwiftyRSA

public typealias Payload = [String: Any]

/// The supported Algorithms
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)
}
}