diff --git a/GeneXusJWT/src/main/java/com/genexus/JWT/JWTCreator.java b/GeneXusJWT/src/main/java/com/genexus/JWT/JWTCreator.java index a4fc6f2..513898e 100644 --- a/GeneXusJWT/src/main/java/com/genexus/JWT/JWTCreator.java +++ b/GeneXusJWT/src/main/java/com/genexus/JWT/JWTCreator.java @@ -1,5 +1,7 @@ package com.genexus.JWT; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -11,6 +13,8 @@ import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.JWTVerifier; import com.auth0.jwt.interfaces.Verification; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import com.genexus.JWT.claims.Claim; import com.genexus.JWT.claims.PrivateClaims; import com.genexus.JWT.claims.PublicClaims; @@ -25,17 +29,18 @@ import com.genexus.securityapicommons.keys.PrivateKeyManager; import com.genexus.securityapicommons.utils.SecurityUtils; - - public class JWTCreator extends JWTObject { + private int counter; + public JWTCreator() { super(); EncodingUtil eu = new EncodingUtil(); eu.setEncoding("UTF8"); - + this.counter = 0; + } - + /******** EXTERNAL OBJECT PUBLIC METHODS - BEGIN ********/ public String doCreate(String algorithm, PrivateClaims privateClaims, JWTOptions options) { if (options.hasError()) { @@ -98,7 +103,7 @@ public boolean doVerify(String token, String expectedAlgorithm, PrivateClaims pr this.error.setError("JW005", e.getMessage()); return false; } - if (isRevoqued(decodedJWT, options) || !verifyPrivateClaims(decodedJWT, privateClaims)) { + if (isRevoqued(decodedJWT, options) || !verifyPrivateClaims(decodedJWT, privateClaims, options)) { return false; } String algorithm = decodedJWT.getAlgorithm(); @@ -107,12 +112,11 @@ public boolean doVerify(String token, String expectedAlgorithm, PrivateClaims pr return false; } JWTAlgorithm expectedJWTAlgorithm = JWTAlgorithm.getJWTAlgorithm(expectedAlgorithm, this.error); - if(alg.compareTo(expectedJWTAlgorithm) != 0 || this.hasError()) - { - this.error.setError("JW008", "Expected algorithm does not match token algorithm"); - return false; - } - + if (alg.compareTo(expectedJWTAlgorithm) != 0 || this.hasError()) { + this.error.setError("JW008", "Expected algorithm does not match token algorithm"); + return false; + } + Algorithm algorithmType = null; if (JWTAlgorithm.isPrivate(alg)) { CertificateX509 cert = options.getCertificate(); @@ -150,7 +154,7 @@ public boolean doVerify(String token, String expectedAlgorithm, PrivateClaims pr error.setError("JW006", e.getMessage()); return false; } - + return true; } @@ -236,7 +240,11 @@ private Builder doBuildPayload(Builder tokenBuilder, PrivateClaims privateClaims List privateC = privateClaims.getAllClaims(); for (int i = 0; i < privateC.size(); i++) { try { - tokenBuilder.withClaim(privateC.get(i).getKey(), privateC.get(i).getValue()); + if (privateC.get(i).getNestedClaims() != null) { + tokenBuilder.withClaim(privateC.get(i).getKey(), privateC.get(i).getNestedClaims().getNestedMap()); + } else { + tokenBuilder.withClaim(privateC.get(i).getKey(), privateC.get(i).getValue()); + } } catch (Exception e) { this.error.setError("JW004", e.getMessage()); return null; @@ -275,32 +283,107 @@ private Builder doBuildPayload(Builder tokenBuilder, PrivateClaims privateClaims // ****END BUILD PAYLOAD****// return tokenBuilder; } - - private boolean verifyPrivateClaims(DecodedJWT decodedJWT, PrivateClaims privateClaims) - { - if(privateClaims == null || privateClaims.isEmpty()) - { + + private boolean verifyPrivateClaims(DecodedJWT decodedJWT, PrivateClaims privateClaims, JWTOptions options) { + RegisteredClaims registeredClaims = options.getAllRegisteredClaims(); + PublicClaims publicClaims = options.getAllPublicClaims(); + if (privateClaims == null || privateClaims.isEmpty()) { return true; } - Map map = decodedJWT.getClaims(); - - List claims = privateClaims.getAllClaims(); - for(int i= 0; i < claims.size(); i++) - { - Claim c = claims.get(i); - if(!map.containsKey(c.getKey())) - { - return false; - } - com.auth0.jwt.interfaces.Claim claim = map.get(c.getKey()); - if(!SecurityUtils.compareStrings(claim.asString().trim(), c.getValue().trim())) - { - return false; + String base64Part = decodedJWT.getPayload(); + byte[] base64Bytes = Base64.decodeBase64(base64Part); + EncodingUtil eu = new EncodingUtil(); + String plainTextPart = eu.getString(base64Bytes); + HashMap map = new HashMap(); + ObjectMapper mapper = new ObjectMapper(); + + try { + map = mapper.readValue(plainTextPart, new TypeReference>() { + }); + } catch (Exception e) { + this.error.setError("JW009", "Cannot parse JWT payload"); + return false; + } + this.counter = 0; + boolean validation = verifyNestedClaims(privateClaims.getNestedMap(), map, registeredClaims, publicClaims); + int pClaimsCount = countingPrivateClaims(privateClaims.getNestedMap(), 0); + if (validation && !(this.counter == pClaimsCount)) { + return false; + } + return validation; + } + + private boolean verifyNestedClaims(Map pclaimMap, Map map, + RegisteredClaims registeredClaims, PublicClaims publicClaims) { + List mapClaimKeyList = new ArrayList(map.keySet()); + List pClaimKeyList = new ArrayList(pclaimMap.keySet()); + if (pClaimKeyList.size() > pClaimKeyList.size()) { + return false; + } + for (String mapKey : mapClaimKeyList) { + + if (!isRegistered(mapKey, registeredClaims) && !isPublic(mapKey, publicClaims)) { + + this.counter++; + if (!pclaimMap.containsKey(mapKey)) { + return false; + } + + Object op = pclaimMap.get(mapKey); + Object ot = map.get(mapKey); + + if (op instanceof String && ot instanceof String) { + + if (!SecurityUtils.compareStrings(((String) op).trim(), ((String) ot).trim())) { + return false; + } + } else if (op instanceof HashMap && ot instanceof HashMap) { + @SuppressWarnings("unchecked") + boolean flag = verifyNestedClaims((HashMap) op, (HashMap) ot, + registeredClaims, publicClaims); + if (!flag) { + return false; + } + } else { + return false; + } } } return true; } - + private boolean isRegistered(String claimKey, RegisteredClaims registeredClaims) { + + List registeredClaimsList = registeredClaims.getAllClaims(); + for (Claim s : registeredClaimsList) { + if (SecurityUtils.compareStrings(s.getKey().trim(), claimKey.trim())) { + return true; + } + } + return false; + } + + private boolean isPublic(String claimKey, PublicClaims publicClaims) { + List publicClaimsList = publicClaims.getAllClaims(); + for (Claim s : publicClaimsList) { + if (SecurityUtils.compareStrings(s.getKey().trim(), claimKey.trim())) { + return true; + } + } + return false; + } + + @SuppressWarnings("unchecked") + private int countingPrivateClaims(Map map, int counter) { + List list = new ArrayList(map.keySet()); + for (String s : list) { + counter++; + Object obj = map.get(s); + if (obj instanceof HashMap) { + counter = countingPrivateClaims((HashMap) obj, counter); + } + } + return counter; + } } diff --git a/GeneXusJWT/src/main/java/com/genexus/JWT/claims/Claim.java b/GeneXusJWT/src/main/java/com/genexus/JWT/claims/Claim.java index 1e844d9..7d2cdc5 100644 --- a/GeneXusJWT/src/main/java/com/genexus/JWT/claims/Claim.java +++ b/GeneXusJWT/src/main/java/com/genexus/JWT/claims/Claim.java @@ -3,15 +3,27 @@ public class Claim { private String key; - private String value; + private Object value; - public Claim(String valueKey, String valueOfValue) { + public Claim(String valueKey, Object valueOfValue) { key = valueKey; value = valueOfValue; - } + } public String getValue() { - return value; + if (value instanceof String) { + return (String) value; + } else { + return null; + } + } + + public PrivateClaims getNestedClaims() { + if (value instanceof PrivateClaims) { + return (PrivateClaims) value; + } else { + return null; + } } public String getKey() { diff --git a/GeneXusJWT/src/main/java/com/genexus/JWT/claims/Claims.java b/GeneXusJWT/src/main/java/com/genexus/JWT/claims/Claims.java index bfcc10f..32f01f7 100644 --- a/GeneXusJWT/src/main/java/com/genexus/JWT/claims/Claims.java +++ b/GeneXusJWT/src/main/java/com/genexus/JWT/claims/Claims.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.List; -import com.genexus.JWT.utils.JWTUtils; import com.genexus.securityapicommons.commons.Error; import com.genexus.securityapicommons.utils.SecurityUtils; @@ -13,9 +12,9 @@ public class Claims { public Claims() { claims = new ArrayList(); - } + } - public boolean setClaim(String key, String value, Error error) { + public boolean setClaim(String key, Object value, Error error) { Claim claim = new Claim(key, value); claims.add(claim); return true; diff --git a/GeneXusJWT/src/main/java/com/genexus/JWT/claims/PrivateClaims.java b/GeneXusJWT/src/main/java/com/genexus/JWT/claims/PrivateClaims.java index 4df21d9..422fe05 100644 --- a/GeneXusJWT/src/main/java/com/genexus/JWT/claims/PrivateClaims.java +++ b/GeneXusJWT/src/main/java/com/genexus/JWT/claims/PrivateClaims.java @@ -1,6 +1,8 @@ package com.genexus.JWT.claims; +import java.util.HashMap; import java.util.List; +import java.util.Map; import com.genexus.securityapicommons.commons.Error; @@ -8,11 +10,33 @@ public final class PrivateClaims extends Claims { private List claims; + public PrivateClaims() { super(); + } - + public boolean setClaim(String key, String value) { return super.setClaim(key, value, new Error()); } + + public boolean setClaim(String key, PrivateClaims value) + { + + return super.setClaim(key, value, new Error()); + } + + public Map getNestedMap() { + HashMap result = new HashMap(); + for (Claim c : getAllClaims()) { + if (c.getValue() != null) { + result.put(c.getKey(), c.getValue()); + } else { + result.put(c.getKey(), ((PrivateClaims) c.getNestedClaims()).getNestedMap()); + } + } + + return result; + } + } diff --git a/GeneXusJWT/src/main/java/com/genexus/JWT/claims/RegisteredClaims.java b/GeneXusJWT/src/main/java/com/genexus/JWT/claims/RegisteredClaims.java index e8d990d..c98c49b 100644 --- a/GeneXusJWT/src/main/java/com/genexus/JWT/claims/RegisteredClaims.java +++ b/GeneXusJWT/src/main/java/com/genexus/JWT/claims/RegisteredClaims.java @@ -3,7 +3,6 @@ import java.util.HashMap; import java.util.List; -import com.genexus.JWT.utils.JWTUtils; import com.genexus.securityapicommons.commons.Error; import com.genexus.securityapicommons.utils.SecurityUtils; @@ -15,10 +14,15 @@ public final class RegisteredClaims extends Claims { public RegisteredClaims() { super(); customTimeValidationClaims = new HashMap(); - + } @Override + public boolean setClaim(String key, Object value, Error error) { + error.setError("RC001", "Not alllowed data type"); + return false; + } + public boolean setClaim(String key, String value, Error error) { if (RegisteredClaim.exists(key)) { return super.setClaim(key, value, error);