Skip to content

Commit 8ee8b08

Browse files
committed
Issue # 18011 (Enhance JWT class constructor to accept cryptoKey and improve error handling in decode, generate, and validate functions)
1 parent a4a73cd commit 8ee8b08

File tree

3 files changed

+76
-47
lines changed

3 files changed

+76
-47
lines changed

Project/Sources/Classes/JWT.4dm

Lines changed: 68 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
/*
2-
Largely inspired by Tech Note: "JSON Web Tokens in 4D"
3-
See: https://kb.4d.com/assetid=79100
4-
*/
5-
61
property _header : Object
72
property _payload : Object
3+
property _cryptoKey : 4D.CryptoKey
84

9-
Class constructor()
5+
Class constructor($inParam : Variant)
106

117
This._header:={}
128
This._payload:={}
139

10+
Case of
11+
: ((Value type($inParam)=Is object) && OB Instance of($inParam; 4D.CryptoKey))
12+
This._cryptoKey:=$inParam
13+
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
16+
17+
Else
18+
This._cryptoKey:=Null
19+
End case
1420

1521
// Mark: - [Public]
1622
// ----------------------------------------------------
@@ -19,7 +25,7 @@ Class constructor()
1925
Function decode($inToken : Text) : Object
2026

2127
Case of
22-
: ((Value type(inToken)#Is text) || (Length(String(inToken))=0))
28+
: ((Value type($inToken)#Is text) || (Length(String($inToken))=0))
2329
This._throwError(9; {which: "\"$inToken\""; function: "JWT.decode"})
2430

2531
Else
@@ -52,9 +58,6 @@ Function generate($inParams : Object; $inPrivateKey : Text) : Text
5258
: ((Value type($inParams.payload)#Is object) || (OB Is empty($inParams.payload)))
5359
This._throwError(9; {which: "\"$inParams.payload\""; function: "JWT.generate"})
5460

55-
: ((Value type($inPrivateKey)#Is text) || (Length(String($inPrivateKey))=0))
56-
This._throwError(9; {which: "\"$inPrivateKey\""; function: "JWT.generate"})
57-
5861
Else
5962
var $alg : Text:=((Value type($inParams.header.alg)=Is text) && (Length($inParams.header.alg)>0)) ? $inParams.header.alg : "RS256"
6063
var $typ : Text:=((Value type($inParams.header.typ)=Is text) && (Length($inParams.header.typ)>0)) ? $inParams.header.typ : "JWT"
@@ -84,9 +87,17 @@ Function generate($inParams : Object; $inPrivateKey : Text) : Text
8487

8588
// Generate Verify Signature Hash based on Algorithm
8689
If ($algorithm="HS@")
87-
$signature:=This._hashHS(This; $inPrivateKey) // HMAC Hash
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
8895
Else
89-
$signature:=This._hashSign(This; $inPrivateKey) // All other Hashes
96+
If ((This._cryptoKey=Null) && ((Value type($inPrivateKey)#Is text) || (Length(String($inPrivateKey))=0)))
97+
This._throwError(9; {which: "\"$inPrivateKey\""; function: "JWT.generate"})
98+
Else
99+
$signature:=This._hashSign(This; $inPrivateKey) // All other Hashes
100+
End if
90101
End if
91102

92103
// Combine Encoded Header and Payload with Hashed Signature for the Token
@@ -106,9 +117,6 @@ Function validate($inJWT : Text; $inKey : Text) : Boolean
106117
: ((Value type($inJWT)#Is text) || (Length(String($inJWT))=0))
107118
This._throwError(9; {which: "\"$inJWT\""; function: "JWT.validate"})
108119

109-
: ((Value type($inKey)#Is text) || (Length(String($inKey))=0))
110-
This._throwError(9; {which: "\"$inKey\""; function: "JWT.validate"})
111-
112120
Else
113121
// Split Token into the three parts: Header, Payload, Verify Signature
114122
var $parts : Collection:=Split string($inJWT; ".")
@@ -132,15 +140,26 @@ Function validate($inJWT : Text; $inKey : Text) : Boolean
132140

133141
var $algorithm : Text:=This._header.alg
134142
If ($algorithm="HS@")
135-
$signature:=This._hashHS($jwt; $key) // HMAC Hash
136-
return ($signature=$parts[2])
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
137149
Else
138-
// Prepare CryptoKey settings
139-
var $settings : Object:={type: "PEM"; pem: $key} // Use specified PEM format Key
140-
var $cryptoKey : 4D.CryptoKey:=4D.CryptoKey.new($settings)
141-
If ($cryptoKey#Null)
142-
var $result : Object:=$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"})
143-
return Bool($result.success)
150+
If ((This._cryptoKey=Null) && ((Value type($inKey)#Is text) || (Length(String($inKey))=0)))
151+
This._throwError(9; {which: "\"$inKey\""; function: "JWT.validate"})
152+
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
144163
End if
145164
End if
146165

@@ -218,36 +237,38 @@ Function _hashHS($inJWT : cs.NetKit.JWT; $inPrivateKey : Text) : Text
218237

219238
Function _hashSign($inJWT : cs.NetKit.JWT; $inPrivateKey : Text) : Text
220239

221-
var $hash; $encodedHead; $encodedPayload : Text
222-
var $settings : Object
223-
var $privateKey : Text:=((Value type($inPrivateKey)=Is text) && (Length($inPrivateKey)>0)) ? $inPrivateKey : ""
224-
225-
// Encode Header and Payload to build Message
226-
BASE64 ENCODE(JSON Stringify($inJWT._header); $encodedHead; *)
227-
BASE64 ENCODE(JSON Stringify($inJWT._payload); $encodedPayload; *)
240+
var $hash : Text
228241

229-
// Prepare CryptoKey settings
230-
If (Length($privateKey)=0)
231-
$settings:={type: "RSA"} // 4D will automatically create RSA key pair
242+
If ((This._cryptoKey=Null) && ((Value type($inPrivateKey)#Is text) || (Length(String($inPrivateKey))=0)))
243+
This._throwError(9; {which: "\"$inPrivateKey\""; function: "JWT.validate"})
232244
Else
233-
$settings:={type: "PEM"; pem: $privateKey} // Use specified PEM format Key
234-
End if
235-
236-
// Create new CryptoKey
237-
var $cryptoKey : 4D.CryptoKey:=4D.CryptoKey.new($settings)
238-
If ($cryptoKey#Null)
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
239249

240-
// Parse Header for Algorithm Family
241-
var $algorithm : Text:=Substring($inJWT._header.alg; 3)
242-
var $hashAlgorithm : Integer
243-
If ($algorithm="256")
244-
$hashAlgorithm:=SHA256 digest
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"})
245268
Else
246-
$hashAlgorithm:=SHA512 digest
269+
This._throwError(15) // The private or public key doesn't seem to be valid PEM.
247270
End if
248271

249-
// Sign Message with CryptoKey to generate hashed verify signature
250-
$hash:=$cryptoKey.sign(String($encodedHead+"."+$encodedPayload); {hash: $hashAlgorithm; pss: Bool($inJWT._header.alg="PS@"); encoding: "Base64URL"})
251272
End if
252273

253274
return $hash

Resources/en.lproj/NetKitEN.xlf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@
7373
<source>The '{which}' parameter in function {function} is not of type {type}.</source>
7474
<target>The '{which}' parameter in function {function} is not of type {type}.</target>
7575
</trans-unit>
76+
<trans-unit resname="ERR_4DNK_15">
77+
<source>The private or public key doesn't seem to be valid PEM.</source>
78+
<target>The private or public key doesn't seem to be valid PEM.</target>
79+
</trans-unit>
7680
<trans-unit resname="OAuth2_Response_Title">
7781
<target>4D NetKit Component</target>
7882
<source>4D NetKit Component</source>

Resources/fr.lproj/NetKitFR.xlf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@
7373
<source>The '{which}' parameter in function {function} is not of type {type}.</source>
7474
<target>Le paramètre '{which}' dans la fonction {function} n'est pas du type {type}.</target>
7575
</trans-unit>
76+
<trans-unit resname="ERR_4DNK_15">
77+
<source>The private or public key doesn't seem to be valid PEM.</source>
78+
<target>La clé privée ou publique ne semble pas être une clé PEM valide.</target>
79+
</trans-unit>
7680
<trans-unit resname="OAuth2_Response_Title">
7781
<source>4D NetKit Component</source>
7882
<target>Composant 4D NetKit</target>

0 commit comments

Comments
 (0)