Skip to content

Commit c1d5325

Browse files
committed
Github:17805 (Refactor JWT class to improve key handling and enhance decode, generate, and validate functions)
1 parent 33a933f commit c1d5325

File tree

1 file changed

+110
-102
lines changed

1 file changed

+110
-102
lines changed

Project/Sources/Classes/JWT.4dm

Lines changed: 110 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,46 @@
1-
property _header : Object
2-
property _payload : Object
3-
property _cryptoKey : 4D.CryptoKey
1+
property key : 4D.CryptoKey
42

53
Class constructor($inParam : Variant)
64

7-
This._header:={}
8-
This._payload:={}
5+
This.key:=This._getCryptoKey($inParam)
96

7+
8+
// Mark: - [Private]
9+
// ----------------------------------------------------
10+
11+
12+
Function _getCryptoKey($inKey : Variant; $inDefaultKey : 4D.CryptoKey) : 4D.CryptoKey
13+
14+
var $key : 4D.CryptoKey
1015
Case of
11-
: ((Value type($inParam)=Is object) && OB Instance of($inParam; 4D.CryptoKey))
12-
This._cryptoKey:=$inParam
16+
: ((Value type($inKey)=Is object) && OB Instance of($inKey; 4D.CryptoKey))
17+
$key:=$inKey // Use specified CryptoKey object
1318

14-
: ((Value type($inParam)=Is text) && (Length(String($inParam))>0))
15-
This._cryptoKey:=Try(4D.CryptoKey.new({type: "PEM"; pem: $inParam})) // Use specified PEM format Key
19+
: ((Value type($inKey)=Is text) && (Length(String($inKey))>0))
20+
$key:=Try(4D.CryptoKey.new({type: "PEM"; pem: $inKey})) // Use specified PEM format Key
1621

1722
Else
18-
This._cryptoKey:=Null
23+
$key:=Null
1924
End case
2025

26+
// If no valid key provided, use default key if provided
27+
If (($key=Null) && ((Value type($inDefaultKey)=Is object) && (OB Instance of($inDefaultKey; 4D.CryptoKey))))
28+
$key:=$inDefaultKey
29+
End if
30+
31+
return $key
32+
33+
2134
// Mark: - [Public]
2235
// ----------------------------------------------------
2336

2437

2538
Function decode($inToken : Text) : Object
2639

40+
var $header : Object:=Null
41+
var $payload : Object:=Null
42+
var signature : Text
43+
2744
Case of
2845
: ((Value type($inToken)#Is text) || (Length(String($inToken))=0))
2946
This._throwError(9; {which: "\"$inToken\""; function: "JWT.decode"})
@@ -32,25 +49,24 @@ Function decode($inToken : Text) : Object
3249
var $parts : Collection:=Split string($inToken; ".")
3350

3451
If ($parts.length>2)
35-
var $header; $payload; $signature : Text
36-
BASE64 DECODE($parts[0]; $header; *)
37-
BASE64 DECODE($parts[1]; $payload; *)
52+
var $decodedHeader; $decodedPayload; $signature : Text
53+
BASE64 DECODE($parts[0]; $decodedHeader; *)
54+
BASE64 DECODE($parts[1]; $decodedPayload; *)
3855
$signature:=$parts[2]
3956

4057
// Note: If JSON parsing fails, Try(JSON Parse(...)) will return Null for header or payload.
41-
This._header:=Try(JSON Parse($header))
42-
This._payload:=Try(JSON Parse($payload))
43-
return {header: This._header; payload: This._payload; signature: $signature}
58+
$header:=Try(JSON Parse($decodedHeader))
59+
$payload:=Try(JSON Parse($decodedPayload))
4460
End if
4561
End case
4662

47-
return {header: Null; payload: Null}
63+
return {header: $header; payload: $payload; signature: $signature}
4864

4965

5066
// ----------------------------------------------------
5167

5268

53-
Function generate($inParams : Object; $inPrivateKey : Text) : Text
69+
Function generate($inParams : Object; $inKey : Variant) : Text
5470

5571
var $result : Text:=""
5672

@@ -63,46 +79,50 @@ Function generate($inParams : Object; $inPrivateKey : Text) : Text
6379
var $typ : Text:=((Value type($inParams.header.typ)=Is text) && (Length($inParams.header.typ)>0)) ? $inParams.header.typ : "JWT"
6480
var $x5t : Text:=(Value type($inParams.header.x5t)=Is text) ? $inParams.header.x5t : ""
6581

66-
This._header:=((Value type($inParams.header)=Is object) && Not(OB Is empty($inParams.header))) ? $inParams.header : {}
67-
This._payload:=((Value type($inParams.payload)=Is object) && Not(OB Is empty($inParams.payload))) ? $inParams.payload : {}
82+
var $header : Object:=((Value type($inParams.header)=Is object) && Not(OB Is empty($inParams.header))) ? $inParams.header : {}
83+
var $payload : Object:=((Value type($inParams.payload)=Is object) && Not(OB Is empty($inParams.payload))) ? $inParams.payload : {}
6884

69-
If (Value type(This._header.alg)=Is undefined)
70-
This._header.alg:=$alg
85+
If (Value type($header.alg)=Is undefined)
86+
$header.alg:=$alg
7187
End if
72-
If (Value type(This._header.typ)=Is undefined)
73-
This._header.typ:=$typ
88+
If (Value type($header.typ)=Is undefined)
89+
$header.typ:=$typ
7490
End if
75-
If ((Value type(This._header.x5t)=Is undefined) && (Length($x5t)>0))
76-
This._header.x5t:=$x5t
91+
If ((Value type($header.x5t)=Is undefined) && (Length($x5t)>0))
92+
$header.x5t:=$x5t
7793
End if
7894

79-
var $header; $payload; $signature : Text
95+
var $encodedHeader; $encodedPayload; $signature : Text
8096

8197
// Encode the Header and Payload
82-
BASE64 ENCODE(JSON Stringify(This._header); $header; *)
83-
BASE64 ENCODE(JSON Stringify(This._payload); $payload; *)
98+
BASE64 ENCODE(JSON Stringify($header); $encodedHeader; *)
99+
BASE64 ENCODE(JSON Stringify($payload); $encodedPayload; *)
84100

85101
// Parse Header for Algorithm Family
86-
var $algorithm : Text:=This._header.alg
102+
var $algorithm : Text:=$header.alg
103+
var $webToken : Object:={header: $header; payload: $payload}
104+
var $cryptoKey : 4D.CryptoKey:=Null
105+
var $key : Text:=""
106+
If (Value type($inKey)=Is text) && (Length(String($inKey))>0)
107+
$key:=$inKey
108+
End if
87109

88110
// Generate Verify Signature Hash based on Algorithm
89111
If ($algorithm="HS@")
90-
If ((Value type($inPrivateKey)#Is text) || (Length(String($inPrivateKey))=0))
91-
This._throwError(9; {which: "\"$inPrivateKey\""; function: "JWT.generate"})
92-
Else
93-
$signature:=This._hashHS(This; $inPrivateKey) // HMAC Hash
94-
End if
112+
$signature:=This._hashHS($webToken; $key) // HMAC Hash
95113
Else
96-
If ((This._cryptoKey=Null) && ((Value type($inPrivateKey)#Is text) || (Length(String($inPrivateKey))=0)))
97-
This._throwError(9; {which: "\"$inPrivateKey\""; function: "JWT.generate"})
114+
$cryptoKey:=This._getCryptoKey($inKey; This.key)
115+
If ($cryptoKey#Null)
116+
$signature:=This._hashSign($webToken; $cryptoKey) // All other Hashes
98117
Else
99-
$signature:=This._hashSign(This; $inPrivateKey) // All other Hashes
118+
This._throwError(15) // The private or public key doesn't seem to be valid PEM.
100119
End if
101120
End if
102121

103122
// Combine Encoded Header and Payload with Hashed Signature for the Token
104-
$result:=$header+"."+$payload+"."+$signature
105-
123+
If (Length($signature)>0)
124+
$result:=$encodedHeader+"."+$encodedPayload+"."+$signature
125+
End if
106126
End case
107127

108128
return $result
@@ -111,7 +131,9 @@ Function generate($inParams : Object; $inPrivateKey : Text) : Text
111131
// ----------------------------------------------------
112132

113133

114-
Function validate($inJWT : Text; $inKey : Text) : Boolean
134+
Function validate($inJWT : Text; $inKey : Variant) : Boolean
135+
136+
var $success : Boolean:=False
115137

116138
Case of
117139
: ((Value type($inJWT)#Is text) || (Length(String($inJWT))=0))
@@ -124,68 +146,57 @@ Function validate($inJWT : Text; $inKey : Text) : Boolean
124146
If ($parts.length>2)
125147

126148
var $header; $payload; $signature : Text
127-
var $key : Text:=((Value type($inKey)=Is text) && (Length($inKey)>0)) ? $inKey : ""
149+
var $cryptoKey : 4D.CryptoKey:=Null
150+
var $key : Text:=""
151+
If (Value type($inKey)=Is text) && (Length(String($inKey))>0)
152+
$key:=$inKey
153+
End if
128154

129155
// Decode Header and Payload into Objects
130156
BASE64 DECODE($parts[0]; $header; *)
131157
BASE64 DECODE($parts[1]; $payload; *)
132-
var $jwt : Object:={_header: Try(JSON Parse($header)); _payload: Try(JSON Parse($payload))}
158+
var $webToken : Object:={header: Try(JSON Parse($header)); payload: Try(JSON Parse($payload))}
133159

134-
If (OB Is empty(This._header))
135-
This._header:=$jwt._header
136-
End if
137-
If (OB Is empty(This._payload))
138-
This._payload:=$jwt._payload
139-
End if
140-
141-
var $algorithm : Text:=This._header.alg
160+
var $algorithm : Text:=$webToken.header.alg
142161
If ($algorithm="HS@")
143-
If ((Value type($inKey)#Is text) || (Length(String($inKey))=0))
144-
This._throwError(9; {which: "\"$inKey\""; function: "JWT.validate"})
145-
Else
146-
$signature:=This._hashHS($jwt; $key) // HMAC Hash
147-
return ($signature=$parts[2])
148-
End if
162+
$signature:=This._hashHS($webToken; $key) // HMAC Hash
163+
$success:=($signature=$parts[2])
149164
Else
150-
If ((This._cryptoKey=Null) && ((Value type($inKey)#Is text) || (Length(String($inKey))=0)))
151-
This._throwError(9; {which: "\"$inKey\""; function: "JWT.validate"})
165+
$cryptoKey:=This._getCryptoKey($inKey; This.key)
166+
If ($cryptoKey#Null)
167+
var $status : Object
168+
var $message : Text:=$parts[0]+"."+$parts[1]
169+
var $options : Object:={hash: (Substring($webToken.header.alg; 3)="256") ? SHA256 digest : SHA512 digest; pss: Bool($webToken.header.alg="PS@"); encoding: "Base64URL"}
170+
$signature:=$parts[2]
171+
$status:=$cryptoKey.verify($message; $signature; $options)
172+
$success:=$status.success
152173
Else
153-
// Prepare CryptoKey settings
154-
If ((Value type($inKey)=Is text) && (Length(String($inKey))>0))
155-
This._cryptoKey:=Try(4D.CryptoKey.new({type: "PEM"; pem: $key})) // Use specified PEM format Key
156-
End if
157-
If (This._cryptoKey=Null)
158-
This._throwError(15) // The private or public key doesn't seem to be valid PEM.
159-
Else
160-
var $result : Object:=This._cryptoKey.verify(String($parts[0]+"."+$parts[1]); $parts[2]; {hash: (Substring($jwt._header.alg; 3)="256") ? SHA256 digest : SHA512 digest; pss: Bool($jwt._header.alg="PS@"); encoding: "Base64URL"})
161-
return Bool($result.success)
162-
End if
174+
This._throwError(15) // The private or public key doesn't seem to be valid PEM.
163175
End if
164176
End if
165-
166177
End if
167178
End case
168179

169-
return False
180+
return $success
170181

171182

172183
// Mark: - [Private]
173184
// ----------------------------------------------------
174185

175186

176-
Function _hashHS($inJWT : cs.NetKit.JWT; $inPrivateKey : Text) : Text
187+
Function _hashHS($inJWT : Object; $inPrivateKey : Text) : Text
177188

178189
var $encodedHeader; $encodedPayload : Text
179190
var $headerBlob; $payloadBlob; $intermediateBlob; $privateBlob; $dataBlob : Blob
180191
var $blockSize; $i; $byte; $hashAlgorithm : Integer
181192

182193
// Encode Header and Payload to build Message in Blob format
183-
BASE64 ENCODE(JSON Stringify($inJWT._header); $encodedHeader; *)
184-
BASE64 ENCODE(JSON Stringify($inJWT._payload); $encodedPayload; *)
194+
BASE64 ENCODE(JSON Stringify($inJWT.header); $encodedHeader; *)
195+
BASE64 ENCODE(JSON Stringify($inJWT.payload); $encodedPayload; *)
185196
TEXT TO BLOB($encodedHeader+"."+$encodedPayload; $dataBlob; UTF8 text without length)
186197

187198
// Parse Hashing Algorithm From Header
188-
var $algorithm : Text:=Substring($inJWT._header.alg; 3)
199+
var $algorithm : Text:=Substring($inJWT.header.alg; 3)
189200
If ($algorithm="256")
190201
$hashAlgorithm:=SHA256 digest
191202
$blockSize:=64
@@ -235,40 +246,38 @@ Function _hashHS($inJWT : cs.NetKit.JWT; $inPrivateKey : Text) : Text
235246
// ----------------------------------------------------
236247

237248

238-
Function _hashSign($inJWT : cs.NetKit.JWT; $inPrivateKey : Text) : Text
249+
Function _hashSign($inJWT : Object; $inCryptoKey : 4D.CryptoKey) : Text
239250

240251
var $hash : Text
241252

242-
If ((This._cryptoKey=Null) && ((Value type($inPrivateKey)#Is text) || (Length(String($inPrivateKey))=0)))
243-
This._throwError(9; {which: "\"$inPrivateKey\""; function: "JWT.validate"})
253+
var $cryptoKey : 4D.CryptoKey:=Null
254+
If ((Value type($inCryptoKey)=Is object) && (OB Instance of($inCryptoKey; 4D.CryptoKey)))
255+
$cryptoKey:=$inCryptoKey
244256
Else
245-
// Prepare CryptoKey settings
246-
If ((Value type($inPrivateKey)=Is text) && (Length(String($inPrivateKey))>0))
247-
This._cryptoKey:=4D.CryptoKey.new({type: "PEM"; pem: $inPrivateKey}) // Use specified PEM format Key
248-
End if
257+
$cryptoKey:=This.key
258+
End if
259+
260+
If ($cryptoKey#Null)
249261

250-
If (This._cryptoKey#Null)
251-
var $encodedHead; $encodedPayload : Text
252-
253-
// Encode Header and Payload to build Message
254-
BASE64 ENCODE(JSON Stringify($inJWT._header); $encodedHead; *)
255-
BASE64 ENCODE(JSON Stringify($inJWT._payload); $encodedPayload; *)
256-
257-
// Parse Header for Algorithm Family
258-
var $algorithm : Text:=Substring($inJWT._header.alg; 3)
259-
var $hashAlgorithm : Integer
260-
If ($algorithm="256")
261-
$hashAlgorithm:=SHA256 digest
262-
Else
263-
$hashAlgorithm:=SHA512 digest
264-
End if
265-
266-
// Sign Message with CryptoKey to generate hashed verify signature
267-
$hash:=This._cryptoKey.sign(String($encodedHead+"."+$encodedPayload); {hash: $hashAlgorithm; pss: Bool($inJWT._header.alg="PS@"); encoding: "Base64URL"})
262+
var $encodedHead; $encodedPayload : Text
263+
264+
// Encode Header and Payload to build Message
265+
BASE64 ENCODE(JSON Stringify($inJWT.header); $encodedHead; *)
266+
BASE64 ENCODE(JSON Stringify($inJWT.payload); $encodedPayload; *)
267+
268+
// Parse Header for Algorithm Family
269+
var $algorithm : Text:=Substring($inJWT.header.alg; 3)
270+
var $hashAlgorithm : Integer
271+
If ($algorithm="256")
272+
$hashAlgorithm:=SHA256 digest
268273
Else
269-
This._throwError(15) // The private or public key doesn't seem to be valid PEM.
274+
$hashAlgorithm:=SHA512 digest
270275
End if
271276

277+
// Sign Message with CryptoKey to generate hashed verify signature
278+
$hash:=$cryptoKey.sign(String($encodedHead+"."+$encodedPayload); {hash: $hashAlgorithm; pss: Bool($inJWT.header.alg="PS@"); encoding: "Base64URL"})
279+
Else
280+
This._throwError(15) // The private or public key doesn't seem to be valid PEM.
272281
End if
273282

274283
return $hash
@@ -283,4 +292,3 @@ Function _throwError($inCode : Integer; $inParameters : Object)
283292
var $error : Object:=cs.Tools.me.makeError($inCode; $inParameters)
284293
$error.deferred:=True
285294
throw($error)
286-

0 commit comments

Comments
 (0)