refactor cnf verification of refresh tokens#50927
Conversation
| AccessToken.Confirmation cnf = refreshToken.getConfirmation(); | ||
| if (cnf != null) { | ||
| // MTLS certificate binding (x5t#S256) | ||
| if (cnf.getCertThumbprint() != null) { |
| if (cnf.getCertThumbprint() == null && cnf.getKeyThumbprint() == null) { | ||
| throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token confirmation"); |
| AccessToken.Confirmation cnf = refreshToken.getConfirmation(); | ||
| if (cnf != null) { |
| if (cnf.getCertThumbprint() != null) { | ||
| if (!MtlsHoKTokenUtil.verifyTokenBindingWithClientCertificate(refreshToken, request, session)) { |
| // DPoP required for public client but token has no DPoP binding (RFC 9449 §5) | ||
| if (clientConfig.isUseDPoP() && client.isPublicClient()) { | ||
| boolean dpopBound = cnf != null && cnf.getKeyThumbprint() != null && (cnf.getJktType() == null || DPOP_JKT_TYPE.equals(cnf.getJktType())); | ||
| if (!dpopBound) { | ||
| throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "DPoP proof key binding is required"); |
There was a problem hiding this comment.
DPoP and ABCA are alternatives to MTLS HoK, not complements.
| // ABCA attestation present but token has no attestation binding | ||
| if (abcaResult != null) { | ||
| boolean abcaBound = cnf != null && cnf.getKeyThumbprint() != null && ABCA_JKT_TYPE.equals(cnf.getJktType()); | ||
| if (!abcaBound) { | ||
| throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Attestation-based key binding is required for this refresh token"); |
There was a problem hiding this comment.
DPoP and ABCA are alternatives to MTLS HoK, not complements.
Closes keycloak#50459 Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
| // ABCA attestation present but token has no attestation binding | ||
| if (abcaResult != null) { | ||
| boolean abcaBound = cnf != null && cnf.getKeyThumbprint() != null && ABCA_JKT_TYPE.equals(cnf.getJktType()); | ||
| if (!abcaBound) { | ||
| throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Attestation-based key binding is required for this refresh token"); |
There was a problem hiding this comment.
DPoP and ABCA are alternatives to MTLS HoK, not complements.
| EventAssertion.assertSuccess(events.poll()).type(EventType.LOGIN); | ||
|
|
||
| String code = oauth.parseLoginResponse().getCode(); |
There was a problem hiding this comment.
I propose to switch the position of these two methods, it may be a potential flaky test candidate if done otherwise.
| EventAssertion.assertSuccess(events.poll()).type(EventType.LOGIN); | |
| String code = oauth.parseLoginResponse().getCode(); | |
| String code = oauth.parseLoginResponse().getCode(); | |
| EventAssertion.assertSuccess(events.poll()).type(EventType.LOGIN); |
| EventAssertion.assertSuccess(events.poll()).type(EventType.LOGIN); | ||
|
|
||
| String code = oauth.parseLoginResponse().getCode(); |
There was a problem hiding this comment.
please, swap the position, discussed above
| assertEquals(200, response.getStatusCode()); | ||
| refreshTokenString = response.getRefreshToken(); | ||
|
|
||
| events.poll(); |
There was a problem hiding this comment.
if you need to skip 1 event or more you can do
| events.poll(); | |
| events.skip(1); |
|
|
||
| @Test | ||
| public void refreshTokenRejectedAfterEnablingCNFCheck() throws Exception { | ||
| ClientRepresentation clientRep = testAppClient.toRepresentation(); |
There was a problem hiding this comment.
please, consider removing testAppClient and replacing it with oauth.clientResource()
potentially doing it in the whole class, but it's out of scope of this PR, since it's refactoring
Closes keycloak#50459 Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
| ClientRepresentation clientRep = oauth.clientResource().toRepresentation(); | ||
| clientRep.setPublicClient(true); | ||
| oauth.clientResource().update(clientRep); |
| // ABCA attestation present but token has no attestation binding | ||
| if (abcaResult != null) { | ||
| boolean abcaBound = cnf != null && cnf.getKeyThumbprint() != null && ABCA_JKT_TYPE.equals(cnf.getJktType()); | ||
| if (!abcaBound) { | ||
| throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Attestation-based key binding is required for this refresh token"); |
Closes #50459
Extracts the cnf binding logic from
verifyRefreshTokeninto a dedicatedverifyConfirmationBindingmethod.Adds client-config guards so that tokens issued before enabling a binding type (MTLS, DPoP, ABCA)
are rejected, before the PR only MTLS was rejected. Also rejects refresh tokens with a cnf claim with no known binding type.