/
JWTSigner.swift
120 lines (101 loc) · 4.53 KB
/
JWTSigner.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import Crypto
/// A JWT signer.
public final class JWTSigner {
/// Algorithm
public var algorithm: JWTAlgorithm
/// Create a new JWT signer.
public init(algorithm: JWTAlgorithm) {
self.algorithm = algorithm
}
/// Signs the message and returns the UTF8 of this message
///
/// Can be transformed into a String like so:
///
/// let signature = try jwt.sign()
/// guard let string = String(bytes: signed, encoding: .utf8) else {
/// throw ...
/// }
/// print(string)
///
/// - parameters:
/// - jwt: JWT to sign.
/// - returns: Signed JWT data.
public func sign<Payload>(_ jwt: JWT<Payload>) throws -> Data {
let jsonEncoder = JSONEncoder()
jsonEncoder.dateEncodingStrategy = .secondsSince1970
// encode header, copying header struct to mutate alg
var header = jwt.header
header.alg = algorithm.jwtAlgorithmName
let headerData = try jsonEncoder.encode(header)
let encodedHeader = headerData.base64URLEncodedData()
// encode payload
let payloadData = try jsonEncoder.encode(jwt.payload)
let encodedPayload = payloadData.base64URLEncodedData()
// combine header and payload to create signature
let encodedSignature = try signature(header: encodedHeader, payload: encodedPayload)
// yield complete jwt
return encodedHeader + Data([.period]) + encodedPayload + Data([.period]) + encodedSignature
}
/// Generates a signature for the supplied payload and header.
public func signature(header: LosslessDataConvertible, payload: LosslessDataConvertible) throws -> Data {
let message: Data = header.convertToData() + Data([.period]) + payload.convertToData()
let signature = try algorithm.sign(message)
return signature.base64URLEncodedData()
}
/// Generates a signature for the supplied payload and header.
public func verify(_ signature: LosslessDataConvertible, header: LosslessDataConvertible, payload: LosslessDataConvertible) throws -> Bool {
let message: Data = header.convertToData() + Data([.period]) + payload.convertToData()
guard let signature = Data(base64URLEncoded: signature.convertToData()) else {
throw JWTError(identifier: "base64", reason: "JWT signature is not valid base64-url")
}
return try algorithm.verify(signature, signs: message)
}
}
/// MARK: HMAC
extension JWTSigner {
/// Creates an HS256 JWT signer with the supplied key
public static func hs256(key: LosslessDataConvertible) -> JWTSigner {
return hmac(HMAC.SHA256, name: "HS256", key: key)
}
/// Creates an HS384 JWT signer with the supplied key
public static func hs384(key: LosslessDataConvertible) -> JWTSigner {
return hmac(HMAC.SHA384, name: "HS384", key: key)
}
/// Creates an HS512 JWT signer with the supplied key
public static func hs512(key: LosslessDataConvertible) -> JWTSigner {
return hmac(HMAC.SHA512, name: "HS512", key: key)
}
/// Creates an HMAC-based `CustomJWTAlgorithm` and `JWTSigner`.
private static func hmac(_ hmac: HMAC, name: String, key: LosslessDataConvertible) -> JWTSigner {
let alg = CustomJWTAlgorithm(name: name, sign: { plaintext in
return try hmac.authenticate(plaintext, key: key)
}, verify: { signature, plaintext in
return try hmac.authenticate(plaintext, key: key) == signature.convertToData()
})
return .init(algorithm: alg)
}
}
/// MARK: RSA
extension JWTSigner {
/// Creates an RS256 JWT signer with the supplied key
public static func rs256(key: RSAKey) -> JWTSigner {
return rsa(RSA.SHA256, name: "RS256", key: key)
}
/// Creates an RS384 JWT signer with the supplied key
public static func rs384(key: RSAKey) -> JWTSigner {
return rsa(RSA.SHA384, name: "RS384", key: key)
}
/// Creates an RS512 JWT signer with the supplied key
public static func rs512(key: RSAKey) -> JWTSigner {
return rsa(RSA.SHA512, name: "RS512", key: key)
}
/// Creates an RSA-based `CustomJWTAlgorithm` and `JWTSigner`.
private static func rsa(_ rsa: RSA, name: String, key: RSAKey) -> JWTSigner {
let alg = CustomJWTAlgorithm(name: name, sign: { plaintext in
return try rsa.sign(plaintext, key: key)
}, verify: { signature, plaintext in
return try rsa.verify(signature, signs: plaintext, key: key)
})
return .init(algorithm: alg)
}
}