Skip to content

Commit 9d057e7

Browse files
committed
feat: Extract algorithms to JWA
1 parent d9a419a commit 9d057e7

16 files changed

+316
-156
lines changed

JSONWebToken.podspec

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ Pod::Spec.new do |spec|
66
spec.license = { :type => 'BSD', :file => 'LICENSE' }
77
spec.author = { 'Kyle Fuller' => '[email protected]' }
88
spec.source = { :git => 'https://github.com/kylef/JSONWebToken.swift.git', :tag => "#{spec.version}" }
9-
spec.source_files = 'Sources/JWT/*.swift'
9+
spec.source_files = [
10+
'Sources/JWA/HMAC/*.swift',
11+
'Sources/{JWA,JWT}/*.swift',
12+
]
1013
spec.ios.deployment_target = '8.0'
1114
spec.osx.deployment_target = '10.9'
1215
spec.tvos.deployment_target = '9.0'
1316
spec.watchos.deployment_target = '2.0'
1417
spec.requires_arc = true
1518
spec.module_name = 'JWT'
16-
spec.exclude_files = ['Sources/JWT/HMACCryptoSwift.swift']
19+
spec.exclude_files = ['Sources/JWA/HMAC/HMACCryptoSwift.swift']
1720

1821
if ARGV.include?('lint')
1922
spec.pod_target_xcconfig = {

Package.swift

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,33 @@
1+
// swift-tools-version:4.0
2+
13
import PackageDescription
24

35

46
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
5-
let package = Package(
6-
name: "JWT",
7-
dependencies: [
8-
.Package(url: "https://github.com/kylef-archive/CommonCrypto.git", majorVersion: 1),
9-
],
10-
exclude: [
11-
"Sources/JWT/HMACCryptoSwift.swift",
12-
]
13-
)
7+
let dependencies = [
8+
Package.Dependency.package(url: "https://github.com/kylef-archive/CommonCrypto.git", from: "1.0.0"),
9+
]
10+
let excludes = ["HMAC/HMACCryptoSwift.swift"]
11+
let targetDependencies: [Target.Dependency] = []
1412
#else
13+
let dependencies = [
14+
Package.Dependency.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "0.8.0"),
15+
]
16+
let excludes = ["HMAC/HMACCommonCrypto.swift"]
17+
let targetDependencies: [Target.Dependency] = ["CryptoSwift"]
18+
#endif
19+
20+
1521
let package = Package(
1622
name: "JWT",
17-
dependencies: [
18-
.Package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", majorVersion: 0, minor: 8),
23+
products: [
24+
.library(name: "JWT", targets: ["JWT"]),
1925
],
20-
exclude: [
21-
"Sources/JWT/HMACCommonCrypto.swift",
26+
dependencies: dependencies,
27+
targets: [
28+
.target(name: "JWA", dependencies: targetDependencies, exclude: excludes),
29+
.target(name: "JWT", dependencies: ["JWA"]),
30+
.testTarget(name: "JWATests", dependencies: ["JWA"]),
31+
.testTarget(name: "JWTTests", dependencies: ["JWT"]),
2232
]
2333
)
24-
#endif

Sources/JWA/HMAC/HMAC.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import Foundation
2+
3+
4+
final public class HMACAlgorithm: Algorithm {
5+
public let key: Data
6+
public let hash: Hash
7+
8+
public enum Hash {
9+
case sha256
10+
case sha384
11+
case sha512
12+
}
13+
14+
public init(key: Data, hash: Hash) {
15+
self.key = key
16+
self.hash = hash
17+
}
18+
19+
public init?(key: String, hash: Hash) {
20+
guard let key = key.data(using: .utf8) else { return nil }
21+
22+
self.key = key
23+
self.hash = hash
24+
}
25+
26+
public var name: String {
27+
switch hash {
28+
case .sha256:
29+
return "HS256"
30+
case .sha384:
31+
return "HS384"
32+
case .sha512:
33+
return "HS512"
34+
}
35+
}
36+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import Foundation
2+
import CommonCrypto
3+
4+
5+
extension HMACAlgorithm: SignAlgorithm, VerifyAlgorithm {
6+
public func sign(_ message: Data) -> Data {
7+
let context = UnsafeMutablePointer<CCHmacContext>.allocate(capacity: 1)
8+
defer { context.deallocate(capacity: 1) }
9+
10+
key.withUnsafeBytes() { (buffer: UnsafePointer<UInt8>) in
11+
CCHmacInit(context, hash.commonCryptoAlgorithm, buffer, size_t(key.count))
12+
}
13+
14+
message.withUnsafeBytes { (buffer: UnsafePointer<UInt8>) in
15+
CCHmacUpdate(context, buffer, size_t(message.count))
16+
}
17+
18+
var hmac = Array<UInt8>(repeating: 0, count: Int(hash.commonCryptoDigestLength))
19+
CCHmacFinal(context, &hmac)
20+
21+
return Data(hmac)
22+
}
23+
}
24+
25+
26+
extension HMACAlgorithm.Hash {
27+
var commonCryptoAlgorithm: CCHmacAlgorithm {
28+
switch self {
29+
case .sha256:
30+
return CCHmacAlgorithm(kCCHmacAlgSHA256)
31+
case .sha384:
32+
return CCHmacAlgorithm(kCCHmacAlgSHA384)
33+
case .sha512:
34+
return CCHmacAlgorithm(kCCHmacAlgSHA512)
35+
}
36+
}
37+
38+
var commonCryptoDigestLength: Int32 {
39+
switch self {
40+
case .sha256:
41+
return CC_SHA256_DIGEST_LENGTH
42+
case .sha384:
43+
return CC_SHA384_DIGEST_LENGTH
44+
case .sha512:
45+
return CC_SHA512_DIGEST_LENGTH
46+
}
47+
}
48+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Foundation
2+
import CryptoSwift
3+
4+
5+
extension HMACAlgorithm: SignAlgorithm, VerifyAlgorithm {
6+
public func sign(_ message: Data) -> Data {
7+
let mac = HMAC(key: key.bytes, variant: hash.cryptoSwiftVariant)
8+
9+
let result: [UInt8]
10+
do {
11+
result = try mac.authenticate(message.bytes)
12+
} catch {
13+
result = []
14+
}
15+
16+
return Data(bytes: result)
17+
}
18+
}
19+
20+
21+
extension HMACAlgorithm.Hash {
22+
var cryptoSwiftVariant: HMAC.Variant {
23+
switch self {
24+
case .sha256:
25+
return .sha256
26+
case .sha384:
27+
return .sha384
28+
case .sha512:
29+
return .sha512
30+
}
31+
}
32+
}

Sources/JWA/JWA.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Foundation
2+
3+
4+
/// Represents a JSON Web Algorithm (JWA)
5+
/// https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
6+
public protocol Algorithm: class {
7+
var name: String { get }
8+
}
9+
10+
11+
// MARK: Signing
12+
13+
/// Represents a JSON Web Algorithm (JWA) that is capable of signing
14+
public protocol SignAlgorithm: Algorithm {
15+
func sign(_ message: Data) -> Data
16+
}
17+
18+
19+
// MARK: Verifying
20+
21+
/// Represents a JSON Web Algorithm (JWA) that is capable of verifying
22+
public protocol VerifyAlgorithm: Algorithm {
23+
func verify(_ message: Data, signature: Data) -> Bool
24+
}
25+
26+
27+
extension SignAlgorithm {
28+
/// Verify a signature for a message using the algorithm
29+
public func verify(_ message: Data, signature: Data) -> Bool {
30+
return sign(message) == signature
31+
}
32+
}

Sources/JWA/None.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Foundation
2+
3+
4+
/// No Algorithm, i-e, insecure
5+
public final class NoneAlgorithm: Algorithm, SignAlgorithm, VerifyAlgorithm {
6+
public var name: String {
7+
return "none"
8+
}
9+
10+
public init() {}
11+
12+
public func sign(_ message: Data) -> Data {
13+
return Data()
14+
}
15+
}

Sources/JWT/Algorithm.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import Foundation
2+
import JWA
3+
4+
5+
/// Represents a JSON Web Algorithm (JWA)
6+
/// https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
7+
public enum Algorithm: CustomStringConvertible {
8+
/// No Algorithm, i-e, insecure
9+
case none
10+
11+
/// HMAC using SHA-256 hash algorithm
12+
case hs256(Data)
13+
14+
/// HMAC using SHA-384 hash algorithm
15+
case hs384(Data)
16+
17+
/// HMAC using SHA-512 hash algorithm
18+
case hs512(Data)
19+
20+
var algorithm: SignAlgorithm {
21+
switch self {
22+
case .none:
23+
return NoneAlgorithm()
24+
case .hs256(let key):
25+
return HMACAlgorithm(key: key, hash: .sha256)
26+
case .hs384(let key):
27+
return HMACAlgorithm(key: key, hash: .sha384)
28+
case .hs512(let key):
29+
return HMACAlgorithm(key: key, hash: .sha512)
30+
}
31+
}
32+
33+
public var description: String {
34+
return algorithm.name
35+
}
36+
}

Sources/JWT/Decode.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func verifySignature(_ algorithms: [Algorithm], header: JOSEHeader, signingInput
108108

109109
let verifiedAlgorithms = algorithms
110110
.filter { algorithm in algorithm.description == alg }
111-
.filter { algorithm in algorithm.verify(signingInput, signature: signature) }
111+
.filter { algorithm in algorithm.algorithm.verify(signingInput.data(using: .utf8)!, signature: signature) }
112112

113113
if verifiedAlgorithms.isEmpty {
114114
throw InvalidToken.invalidAlgorithm

Sources/JWT/Encode.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Foundation
22

3+
34
/*** Encode a set of claims
45
- parameter claims: The set of claims
56
- parameter algorithm: The algorithm to sign the payload with
@@ -17,7 +18,7 @@ public func encode(claims: ClaimSet, algorithm: Algorithm, headers: [String: Str
1718
let header = try! encoder.encodeString(headers)
1819
let payload = encoder.encodeString(claims.claims)!
1920
let signingInput = "\(header).\(payload)"
20-
let signature = algorithm.sign(signingInput)
21+
let signature = base64encode(algorithm.algorithm.sign(signingInput.data(using: .utf8)!))
2122
return "\(signingInput).\(signature)"
2223
}
2324

0 commit comments

Comments
 (0)