3
3
import static io .quarkus .oidc .runtime .OidcUtils .validateAndCreateIdentity ;
4
4
import static io .quarkus .vertx .http .runtime .security .HttpSecurityUtils .getRoutingContextAttribute ;
5
5
6
+ import java .security .NoSuchAlgorithmException ;
6
7
import java .security .Principal ;
7
8
import java .util .Map ;
8
9
import java .util .Set ;
14
15
15
16
import org .eclipse .microprofile .jwt .Claims ;
16
17
import org .jboss .logging .Logger ;
18
+ import org .jose4j .jwk .PublicJsonWebKey ;
19
+ import org .jose4j .jws .JsonWebSignature ;
20
+ import org .jose4j .lang .JoseException ;
17
21
import org .jose4j .lang .UnresolvableKeyException ;
18
22
19
23
import io .quarkus .oidc .AccessTokenCredential ;
@@ -201,33 +205,120 @@ private Uni<TokenVerificationResult> verifyPrimaryTokenUni(Map<String, Object> r
201
205
final boolean idToken = isIdToken (request );
202
206
Uni <TokenVerificationResult > result = verifyTokenUni (requestData , resolvedContext , request .getToken (), idToken ,
203
207
false , userInfo );
204
- if (!idToken && resolvedContext .oidcConfig ().token ().binding ().certificate ()) {
205
- return result .onItem ().transform (new Function <TokenVerificationResult , TokenVerificationResult >() {
208
+ if (!idToken ) {
209
+ if (resolvedContext .oidcConfig ().token ().binding ().certificate ()) {
210
+ result = result .onItem ().transform (new Function <TokenVerificationResult , TokenVerificationResult >() {
206
211
207
- @ Override
208
- public TokenVerificationResult apply (TokenVerificationResult t ) {
209
- String tokenCertificateThumbprint = getTokenCertThumbprint (requestData , t );
210
- if (tokenCertificateThumbprint == null ) {
211
- LOG .warn (
212
- "Access token does not contain a confirmation 'cnf' claim with the certificate thumbprint" );
213
- throw new AuthenticationFailedException ();
214
- }
215
- String clientCertificateThumbprint = (String ) requestData .get (OidcConstants .X509_SHA256_THUMBPRINT );
216
- if (clientCertificateThumbprint == null ) {
217
- LOG .warn ("Client certificate thumbprint is not available" );
218
- throw new AuthenticationFailedException ();
212
+ @ Override
213
+ public TokenVerificationResult apply (TokenVerificationResult t ) {
214
+ String tokenCertificateThumbprint = getTokenCertThumbprint (requestData , t );
215
+ if (tokenCertificateThumbprint == null ) {
216
+ LOG .warn (
217
+ "Access token does not contain a confirmation 'cnf' claim with the certificate thumbprint" );
218
+ throw new AuthenticationFailedException ();
219
+ }
220
+ String clientCertificateThumbprint = (String ) requestData .get (OidcConstants .X509_SHA256_THUMBPRINT );
221
+ if (clientCertificateThumbprint == null ) {
222
+ LOG .warn ("Client certificate thumbprint is not available" );
223
+ throw new AuthenticationFailedException ();
224
+ }
225
+ if (!clientCertificateThumbprint .equals (tokenCertificateThumbprint )) {
226
+ LOG .warn ("Client certificate thumbprint does not match the token certificate thumbprint" );
227
+ throw new AuthenticationFailedException ();
228
+ }
229
+ return t ;
219
230
}
220
- if (!clientCertificateThumbprint .equals (tokenCertificateThumbprint )) {
221
- LOG .warn ("Client certificate thumbprint does not match the token certificate thumbprint" );
222
- throw new AuthenticationFailedException ();
231
+
232
+ });
233
+ }
234
+
235
+ if (requestData .containsKey (OidcUtils .DPOP_PROOF_JWT_HEADERS )) {
236
+ result = result .onItem ().transform (new Function <TokenVerificationResult , TokenVerificationResult >() {
237
+
238
+ @ Override
239
+ public TokenVerificationResult apply (TokenVerificationResult t ) {
240
+
241
+ String dpopJwkThumbprint = getDpopJwkThumbprint (requestData , t );
242
+ if (dpopJwkThumbprint == null ) {
243
+ LOG .warn (
244
+ "DPoP access token does not contain a confirmation 'cnf' claim with the JWK thumbprint" );
245
+ throw new AuthenticationFailedException ();
246
+ }
247
+
248
+ JsonObject proofHeaders = (JsonObject ) requestData .get (OidcUtils .DPOP_PROOF_JWT_HEADERS );
249
+
250
+ JsonObject jwkProof = proofHeaders .getJsonObject (OidcConstants .DPOP_JWK_HEADER );
251
+ if (jwkProof == null ) {
252
+ LOG .warn ("DPoP proof jwk header is missing" );
253
+ throw new AuthenticationFailedException ();
254
+ }
255
+
256
+ PublicJsonWebKey publicJsonWebKey = null ;
257
+ try {
258
+ publicJsonWebKey = PublicJsonWebKey .Factory .newPublicJwk (jwkProof .getMap ());
259
+ } catch (JoseException ex ) {
260
+ LOG .warn ("DPoP proof jwk header does not represent a valid JWK key" );
261
+ throw new AuthenticationFailedException (ex );
262
+ }
263
+
264
+ if (publicJsonWebKey .getPrivateKey () != null ) {
265
+ LOG .warn ("DPoP proof JWK key is a private key but it must be a public key" );
266
+ throw new AuthenticationFailedException ();
267
+ }
268
+
269
+ byte [] jwkProofDigest = publicJsonWebKey .calculateThumbprint ("SHA-256" );
270
+ String jwkProofThumbprint = OidcCommonUtils .base64UrlEncode (jwkProofDigest );
271
+
272
+ if (!dpopJwkThumbprint .equals (jwkProofThumbprint )) {
273
+ LOG .warn ("DPoP access token JWK thumbprint does not match the DPoP proof JWK thumbprint" );
274
+ throw new AuthenticationFailedException ();
275
+ }
276
+
277
+ try {
278
+ JsonWebSignature jws = new JsonWebSignature ();
279
+ jws .setAlgorithmConstraints (OidcProvider .ASYMMETRIC_ALGORITHM_CONSTRAINTS );
280
+ jws .setCompactSerialization ((String ) requestData .get (OidcUtils .DPOP_PROOF ));
281
+ jws .setKey (publicJsonWebKey .getPublicKey ());
282
+ if (!jws .verifySignature ()) {
283
+ LOG .warn ("DPoP proof token signature is invalid" );
284
+ throw new AuthenticationFailedException ();
285
+ }
286
+ } catch (JoseException ex ) {
287
+ LOG .warn ("DPoP proof token signature can not be verified" );
288
+ throw new AuthenticationFailedException (ex );
289
+ }
290
+
291
+ JsonObject proofClaims = (JsonObject ) requestData .get (OidcUtils .DPOP_PROOF_JWT_CLAIMS );
292
+
293
+ // Calculate the access token thumprint and compare with the `ath` claim
294
+
295
+ String accessTokenProof = proofClaims .getString (OidcConstants .DPOP_ACCESS_TOKEN_THUMBPRINT );
296
+ if (accessTokenProof == null ) {
297
+ LOG .warn ("DPoP proof access token hash is missing" );
298
+ throw new AuthenticationFailedException ();
299
+ }
300
+
301
+ String accessTokenHash = null ;
302
+ try {
303
+ accessTokenHash = OidcCommonUtils .base64UrlEncode (
304
+ OidcUtils .getSha256Digest (request .getToken ().getToken ()));
305
+ } catch (NoSuchAlgorithmException ex ) {
306
+ // SHA256 is always supported
307
+ }
308
+
309
+ if (!accessTokenProof .equals (accessTokenHash )) {
310
+ LOG .warn ("DPoP access token hash does not match the DPoP proof access token hash" );
311
+ throw new AuthenticationFailedException ();
312
+ }
313
+
314
+ return t ;
223
315
}
224
- return t ;
225
- }
226
316
227
- });
228
- } else {
229
- return result ;
317
+ });
318
+ }
230
319
}
320
+
321
+ return result ;
231
322
}
232
323
}
233
324
@@ -243,6 +334,19 @@ private static String getTokenCertThumbprint(Map<String, Object> requestData, To
243
334
return thumbprint ;
244
335
}
245
336
337
+ private static String getDpopJwkThumbprint (Map <String , Object > requestData , TokenVerificationResult t ) {
338
+ JsonObject json = t .localVerificationResult != null ? t .localVerificationResult
339
+ : new JsonObject (t .introspectionResult .getIntrospectionString ());
340
+ JsonObject cnf = json .getJsonObject (OidcConstants .CONFIRMATION_CLAIM );
341
+ String thumbprint = cnf == null ? null : cnf .getString (OidcConstants .DPOP_JWK_SHA256_THUMBPRINT );
342
+ if (thumbprint != null ) {
343
+ requestData .put (
344
+ (t .introspectionResult == null ? OidcUtils .DPOP_JWT_THUMBPRINT : OidcUtils .DPOP_INTROSPECTION_THUMBPRINT ),
345
+ true );
346
+ }
347
+ return thumbprint ;
348
+ }
349
+
246
350
private Uni <SecurityIdentity > getUserInfoAndCreateIdentity (Uni <TokenVerificationResult > tokenUni ,
247
351
Map <String , Object > requestData ,
248
352
TokenAuthenticationRequest request ,
0 commit comments