Skip to content

Commit

Permalink
implemented "ath" claim check on resource server side
Browse files Browse the repository at this point in the history
  • Loading branch information
pasindu lakshan committed Mar 6, 2024
1 parent dc04096 commit a8bf10b
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class DPoPConstants {
public static final String DPOP_ISSUED_AT = "iat";
public static final String DPOP_HTTP_URI = "htu";
public static final String DPOP_HTTP_METHOD = "htm";
public static final String DPOP_ACCESS_TOKEN_HASH = "ath";
public static final String DPOP_JWT_TYPE = "dpop+jwt";
public static final String DPOP_TOKEN_TYPE = "DPoP";
public static final String INVALID_DPOP_PROOF = "invalid_dpop_proof";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public static String getThumbprintOfKeyFromDpopProof(String dPopProof) throws Id
JWSHeader header = signedJwt.getHeader();
return getKeyThumbprintOfKey(header.getJWK().toString(), signedJwt);
} catch (ParseException | JOSEException e) {
throw new IdentityOAuth2ClientException(DPoPConstants.INVALID_DPOP_PROOF, DPoPConstants.INVALID_DPOP_PROOF + " : DPoP roof is not a properly formed JWT.", e);
throw new IdentityOAuth2ClientException(DPoPConstants.INVALID_DPOP_PROOF, DPoPConstants.INVALID_DPOP_PROOF + " : DPoP JWT or JWK claim is not in a valid format.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@
import org.wso2.carbon.identity.oauth2.token.bindings.TokenBinding;
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.Base64;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -110,6 +114,26 @@ public static boolean isValidDPoPProof(String httpMethod, String httpURL, String
return validateDPoPPayload(httpMethod, httpURL, signedJwt.getJWTClaimsSet()) && validateDPoPHeader(header);
}

/**
* Validate dpop proof header.
*
* @param httpMethod HTTP method of the request.
* @param httpURL HTTP URL of the request,
* @param dPoPProof DPoP header of the request.
* @param token Access token / Refresh token.
* @return
* @throws ParseException Error while retrieving the signedJwt.
* @throws IdentityOAuth2Exception Error while validating the dpop proof.
*/
public static boolean isValidDPoPProof(String httpMethod, String httpURL, String dPoPProof, String token)
throws ParseException, IdentityOAuth2Exception {

SignedJWT signedJwt = SignedJWT.parse(dPoPProof);
JWSHeader header = signedJwt.getHeader();

return validateDPoPPayload(httpMethod, httpURL, signedJwt.getJWTClaimsSet(),token) && validateDPoPHeader(header) ;
}

/**
* Set token binder information if dpop proof is valid.
*
Expand Down Expand Up @@ -149,13 +173,22 @@ private static boolean validateDPoPHeader(JWSHeader header) throws IdentityOAuth
return checkJwk(header) && checkAlg(header) && checkHeaderType(header);
}

//Authorization server side validator without "ath" claim validation
private static boolean validateDPoPPayload(String httpMethod, String httpURL, JWTClaimsSet jwtClaimsSet)
throws IdentityOAuth2Exception {

return checkJwtClaimSet(jwtClaimsSet) && checkDPoPHeaderValidity(jwtClaimsSet) && checkJti(jwtClaimsSet) &&
checkHTTPMethod(httpMethod, jwtClaimsSet) && checkHTTPURI(httpURL, jwtClaimsSet);
}

//Resource server side validator with "ath" claim validation
private static boolean validateDPoPPayload(String httpMethod, String httpURL, JWTClaimsSet jwtClaimsSet, String token)
throws IdentityOAuth2Exception {

return checkJwtClaimSet(jwtClaimsSet) && checkDPoPHeaderValidity(jwtClaimsSet) && checkJti(jwtClaimsSet) &&
checkHTTPMethod(httpMethod, jwtClaimsSet) && checkHTTPURI(httpURL, jwtClaimsSet) && checkAth(token, jwtClaimsSet);
}

private static boolean checkJwk(JWSHeader header) throws IdentityOAuth2ClientException {

JWK jwk = header.getJWK();
Expand Down Expand Up @@ -315,4 +348,33 @@ private static void setCnFValue(OAuthTokenReqMessageContext tokReqMsgCtx, String
obj.put(DPoPConstants.JWK_THUMBPRINT, tokenBindingValue);
tokReqMsgCtx.addProperty(DPoPConstants.CNF, obj);
}

private static boolean checkAth(String token, JWTClaimsSet jwtClaimsSet) throws IdentityOAuth2ClientException {

Object ath = jwtClaimsSet.getClaim(DPoPConstants.DPOP_ACCESS_TOKEN_HASH);
if (ath == null) {
String error = "DPoP Proof access token hash is empty.";
if (log.isDebugEnabled()) {
log.error(error);
}
throw new IdentityOAuth2ClientException(DPoPConstants.INVALID_DPOP_PROOF, DPoPConstants.INVALID_DPOP_PROOF +" : " + error);
}
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
log.error("Error while getting the SHA-256 algorithm.", e);
}
byte[] hashBytes = digest.digest(token.getBytes(StandardCharsets.US_ASCII));
// Encode the hash using base64url encoding
String hashFromToken = Base64.getUrlEncoder().withoutPadding().encodeToString(hashBytes);
if (!StringUtils.equals(ath.toString(),hashFromToken)) {
String error = "DPoP Proof access token hash mismatch.";
if (log.isDebugEnabled()) {
log.error(error);
}
throw new IdentityOAuth2ClientException(DPoPConstants.INVALID_DPOP_PROOF, DPoPConstants.INVALID_DPOP_PROOF +" : " + error);
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ private boolean validateDPoP(OAuth2TokenValidationMessageContext validationReqDT
return false;
}

if (!DPoPHeaderValidator.isValidDPoPProof(httpMethod, httpUrl, dpopProof)) {
if (!DPoPHeaderValidator.isValidDPoPProof(httpMethod, httpUrl, dpopProof,accessTokenDO.getAccessToken())) {
return false;
}

Expand Down

0 comments on commit a8bf10b

Please sign in to comment.