11package com .genexus .JWT ;
22
3+ import java .util .ArrayList ;
4+ import java .util .HashMap ;
35import java .util .List ;
46import java .util .Map ;
57
1113import com .auth0 .jwt .interfaces .DecodedJWT ;
1214import com .auth0 .jwt .interfaces .JWTVerifier ;
1315import com .auth0 .jwt .interfaces .Verification ;
16+ import com .fasterxml .jackson .core .type .TypeReference ;
17+ import com .fasterxml .jackson .databind .ObjectMapper ;
1418import com .genexus .JWT .claims .Claim ;
19+ import com .genexus .JWT .claims .HeaderParameters ;
1520import com .genexus .JWT .claims .PrivateClaims ;
1621import com .genexus .JWT .claims .PublicClaims ;
1722import com .genexus .JWT .claims .RegisteredClaim ;
2530import com .genexus .securityapicommons .keys .PrivateKeyManager ;
2631import com .genexus .securityapicommons .utils .SecurityUtils ;
2732
28-
29-
3033public class JWTCreator extends JWTObject {
3134
35+ private int counter ;
36+
3237 public JWTCreator () {
3338 super ();
3439 EncodingUtil eu = new EncodingUtil ();
3540 eu .setEncoding ("UTF8" );
36-
41+ this .counter = 0 ;
42+
3743 }
38-
44+
3945 /******** EXTERNAL OBJECT PUBLIC METHODS - BEGIN ********/
4046 public String doCreate (String algorithm , PrivateClaims privateClaims , JWTOptions options ) {
4147 if (options .hasError ()) {
@@ -47,6 +53,10 @@ public String doCreate(String algorithm, PrivateClaims privateClaims, JWTOptions
4753 return "" ;
4854 }
4955 Builder tokenBuilder = JWT .create ();
56+ if (!options .getHeaderParameters ().isEmpty ()) {
57+ HeaderParameters parameters = options .getHeaderParameters ();
58+ tokenBuilder .withHeader (parameters .getMap ());
59+ }
5060 tokenBuilder = doBuildPayload (tokenBuilder , privateClaims , options );
5161 if (this .hasError ()) {
5262 return "" ;
@@ -98,7 +108,8 @@ public boolean doVerify(String token, String expectedAlgorithm, PrivateClaims pr
98108 this .error .setError ("JW005" , e .getMessage ());
99109 return false ;
100110 }
101- if (isRevoqued (decodedJWT , options ) || !verifyPrivateClaims (decodedJWT , privateClaims )) {
111+ if (isRevoqued (decodedJWT , options ) || !verifyPrivateClaims (decodedJWT , privateClaims , options )
112+ || !verifyHeader (decodedJWT , options )) {
102113 return false ;
103114 }
104115 String algorithm = decodedJWT .getAlgorithm ();
@@ -107,12 +118,11 @@ public boolean doVerify(String token, String expectedAlgorithm, PrivateClaims pr
107118 return false ;
108119 }
109120 JWTAlgorithm expectedJWTAlgorithm = JWTAlgorithm .getJWTAlgorithm (expectedAlgorithm , this .error );
110- if (alg .compareTo (expectedJWTAlgorithm ) != 0 || this .hasError ())
111- {
112- this .error .setError ("JW008" , "Expected algorithm does not match token algorithm" );
113- return false ;
114- }
115-
121+ if (alg .compareTo (expectedJWTAlgorithm ) != 0 || this .hasError ()) {
122+ this .error .setError ("JW008" , "Expected algorithm does not match token algorithm" );
123+ return false ;
124+ }
125+
116126 Algorithm algorithmType = null ;
117127 if (JWTAlgorithm .isPrivate (alg )) {
118128 CertificateX509 cert = options .getCertificate ();
@@ -150,7 +160,7 @@ public boolean doVerify(String token, String expectedAlgorithm, PrivateClaims pr
150160 error .setError ("JW006" , e .getMessage ());
151161 return false ;
152162 }
153-
163+
154164 return true ;
155165
156166 }
@@ -236,7 +246,11 @@ private Builder doBuildPayload(Builder tokenBuilder, PrivateClaims privateClaims
236246 List <Claim > privateC = privateClaims .getAllClaims ();
237247 for (int i = 0 ; i < privateC .size (); i ++) {
238248 try {
239- tokenBuilder .withClaim (privateC .get (i ).getKey (), privateC .get (i ).getValue ());
249+ if (privateC .get (i ).getNestedClaims () != null ) {
250+ tokenBuilder .withClaim (privateC .get (i ).getKey (), privateC .get (i ).getNestedClaims ().getNestedMap ());
251+ } else {
252+ tokenBuilder .withClaim (privateC .get (i ).getKey (), privateC .get (i ).getValue ());
253+ }
240254 } catch (Exception e ) {
241255 this .error .setError ("JW004" , e .getMessage ());
242256 return null ;
@@ -275,32 +289,157 @@ private Builder doBuildPayload(Builder tokenBuilder, PrivateClaims privateClaims
275289 // ****END BUILD PAYLOAD****//
276290 return tokenBuilder ;
277291 }
278-
279- private boolean verifyPrivateClaims (DecodedJWT decodedJWT , PrivateClaims privateClaims )
280- {
281- if (privateClaims == null || privateClaims .isEmpty ())
282- {
292+
293+ private boolean verifyPrivateClaims (DecodedJWT decodedJWT , PrivateClaims privateClaims , JWTOptions options ) {
294+ RegisteredClaims registeredClaims = options .getAllRegisteredClaims ();
295+ PublicClaims publicClaims = options .getAllPublicClaims ();
296+ if (privateClaims == null || privateClaims .isEmpty ()) {
297+ return true ;
298+ }
299+ String base64Part = decodedJWT .getPayload ();
300+ byte [] base64Bytes = Base64 .decodeBase64 (base64Part );
301+ EncodingUtil eu = new EncodingUtil ();
302+ String plainTextPart = eu .getString (base64Bytes );
303+ HashMap <String , Object > map = new HashMap <String , Object >();
304+ ObjectMapper mapper = new ObjectMapper ();
305+
306+ try {
307+ map = (HashMap <String , Object >) mapper .readValue (plainTextPart , new TypeReference <Map <String , Object >>() {
308+ });
309+ } catch (Exception e ) {
310+ this .error .setError ("JW009" , "Cannot parse JWT payload" );
311+ return false ;
312+ }
313+ this .counter = 0 ;
314+ boolean validation = verifyNestedClaims (privateClaims .getNestedMap (), map , registeredClaims , publicClaims );
315+ int pClaimsCount = countingPrivateClaims (privateClaims .getNestedMap (), 0 );
316+ if (validation && !(this .counter == pClaimsCount )) {
317+ return false ;
318+ }
319+ return validation ;
320+ }
321+
322+ private boolean verifyNestedClaims (Map <String , Object > pclaimMap , Map <String , Object > map ,
323+ RegisteredClaims registeredClaims , PublicClaims publicClaims ) {
324+ List <String > mapClaimKeyList = new ArrayList <String >(map .keySet ());
325+ List <String > pClaimKeyList = new ArrayList <String >(pclaimMap .keySet ());
326+ if (pClaimKeyList .size () > pClaimKeyList .size ()) {
327+ return false ;
328+ }
329+ for (String mapKey : mapClaimKeyList ) {
330+
331+ if (!isRegistered (mapKey , registeredClaims ) && !isPublic (mapKey , publicClaims )) {
332+
333+ this .counter ++;
334+ if (!pclaimMap .containsKey (mapKey )) {
335+ return false ;
336+ }
337+
338+ Object op = pclaimMap .get (mapKey );
339+ Object ot = map .get (mapKey );
340+
341+ if (op instanceof String && ot instanceof String ) {
342+
343+ if (!SecurityUtils .compareStrings (((String ) op ).trim (), ((String ) ot ).trim ())) {
344+ return false ;
345+ }
346+ } else if (op instanceof HashMap && ot instanceof HashMap ) {
347+ @ SuppressWarnings ("unchecked" )
348+ boolean flag = verifyNestedClaims ((HashMap <String , Object >) op , (HashMap <String , Object >) ot ,
349+ registeredClaims , publicClaims );
350+ if (!flag ) {
351+ return false ;
352+ }
353+ } else {
354+ return false ;
355+ }
356+ }
357+ }
358+ return true ;
359+ }
360+
361+ private boolean isRegistered (String claimKey , RegisteredClaims registeredClaims ) {
362+
363+ List <Claim > registeredClaimsList = registeredClaims .getAllClaims ();
364+ for (Claim s : registeredClaimsList ) {
365+ if (SecurityUtils .compareStrings (s .getKey ().trim (), claimKey .trim ())) {
366+ return true ;
367+ }
368+ }
369+ return false ;
370+ }
371+
372+ private boolean isPublic (String claimKey , PublicClaims publicClaims ) {
373+ List <Claim > publicClaimsList = publicClaims .getAllClaims ();
374+ for (Claim s : publicClaimsList ) {
375+ if (SecurityUtils .compareStrings (s .getKey ().trim (), claimKey .trim ())) {
376+ return true ;
377+ }
378+ }
379+ return false ;
380+ }
381+
382+ @ SuppressWarnings ("unchecked" )
383+ private int countingPrivateClaims (Map <String , Object > map , int counter ) {
384+ List <String > list = new ArrayList <String >(map .keySet ());
385+ for (String s : list ) {
386+ counter ++;
387+ Object obj = map .get (s );
388+ if (obj instanceof HashMap ) {
389+ counter = countingPrivateClaims ((HashMap <String , Object >) obj , counter );
390+ }
391+ }
392+ return counter ;
393+ }
394+
395+ private boolean verifyHeader (DecodedJWT decodedJWT , JWTOptions options ) {
396+ HeaderParameters parameters = options .getHeaderParameters ();
397+ if (parameters .isEmpty ()) {
283398 return true ;
284399 }
285- Map <String , com .auth0 .jwt .interfaces .Claim > map = decodedJWT .getClaims ();
286-
287- List <Claim > claims = privateClaims .getAllClaims ();
288- for (int i = 0 ; i < claims .size (); i ++)
289- {
290- Claim c = claims .get (i );
291- if (!map .containsKey (c .getKey ()))
292- {
400+
401+ List <String > allParms = parameters .getAll ();
402+ if (allParms .size () + 2 != getHeaderClaimsNumber (decodedJWT )) {
403+ return false ;
404+ }
405+ Map <String , Object > map = parameters .getMap ();
406+ for (String s : allParms ) {
407+
408+ if (decodedJWT .getHeaderClaim (s ) == null ) {
293409 return false ;
294410 }
295- com .auth0 .jwt .interfaces .Claim claim = map .get (c .getKey ());
296- if (!SecurityUtils .compareStrings (claim .asString ().trim (), c .getValue ().trim ()))
297- {
411+ com .auth0 .jwt .interfaces .Claim c = decodedJWT .getHeaderClaim (s );
412+ String claimValue = null ;
413+ try {
414+ claimValue = c .asString ().trim ();
415+ } catch (NullPointerException e ) {
416+ return false ;
417+ }
418+ String optionsValue = ((String ) map .get (s )).trim ();
419+ if (!SecurityUtils .compareStrings (claimValue , optionsValue )) {
298420 return false ;
299421 }
300422 }
301423 return true ;
424+
302425 }
303-
304426
427+ private int getHeaderClaimsNumber (DecodedJWT decodedJWT ) {
428+ String base64Part = decodedJWT .getHeader ();
429+ byte [] base64Bytes = Base64 .decodeBase64 (base64Part );
430+ EncodingUtil eu = new EncodingUtil ();
431+ String plainTextPart = eu .getString (base64Bytes );
432+ HashMap <String , Object > map = new HashMap <String , Object >();
433+ ObjectMapper mapper = new ObjectMapper ();
434+
435+ try {
436+ map = (HashMap <String , Object >) mapper .readValue (plainTextPart , new TypeReference <Map <String , Object >>() {
437+ });
438+ } catch (Exception e ) {
439+ return 0 ;
440+ }
441+ return map .size ();
442+
443+ }
305444
306445}
0 commit comments