20
20
import com .nimbusds .jwt .JWTClaimsSet ;
21
21
import com .nimbusds .jwt .SignedJWT ;
22
22
import com .nimbusds .jwt .util .DateUtils ;
23
+ import org .apache .commons .codec .binary .Base64 ;
24
+ import org .apache .commons .lang3 .ArrayUtils ;
23
25
import org .apache .commons .lang3 .StringUtils ;
24
26
import org .apache .commons .logging .Log ;
25
27
import org .apache .commons .logging .LogFactory ;
26
28
import org .apache .cxf .message .Message ;
27
29
import org .wso2 .carbon .apimgt .api .APIManagementException ;
28
30
import org .wso2 .carbon .apimgt .api .OAuthTokenInfo ;
29
- import org .wso2 .carbon .apimgt .api .dto .KeyManagerConfigurationDTO ;
30
31
import org .wso2 .carbon .apimgt .common .gateway .constants .JWTConstants ;
32
+ import org .wso2 .carbon .apimgt .common .gateway .dto .JWKSConfigurationDTO ;
31
33
import org .wso2 .carbon .apimgt .common .gateway .dto .JWTValidationInfo ;
32
34
import org .wso2 .carbon .apimgt .common .gateway .dto .TokenIssuerDto ;
33
35
import org .wso2 .carbon .apimgt .impl .APIConstants ;
36
38
import org .wso2 .carbon .apimgt .impl .dto .KeyManagerDto ;
37
39
import org .wso2 .carbon .apimgt .impl .factory .KeyManagerHolder ;
38
40
import org .wso2 .carbon .apimgt .impl .jwt .JWTValidator ;
41
+ import org .wso2 .carbon .apimgt .impl .jwt .JWTValidatorImpl ;
39
42
import org .wso2 .carbon .apimgt .impl .jwt .SignedJWTInfo ;
40
43
import org .wso2 .carbon .apimgt .impl .utils .APIUtil ;
41
44
import org .wso2 .carbon .apimgt .rest .api .common .APIMConfigUtil ;
45
48
import org .wso2 .carbon .apimgt .rest .api .util .authenticators .AbstractOAuthAuthenticator ;
46
49
import org .wso2 .carbon .apimgt .rest .api .util .utils .RestApiUtil ;
47
50
import org .wso2 .carbon .context .PrivilegedCarbonContext ;
51
+ import org .wso2 .carbon .identity .application .common .model .FederatedAuthenticatorConfig ;
52
+ import org .wso2 .carbon .identity .application .common .model .IdentityProvider ;
53
+ import org .wso2 .carbon .identity .application .common .model .IdentityProviderProperty ;
54
+ import org .wso2 .carbon .identity .application .common .util .IdentityApplicationConstants ;
55
+ import org .wso2 .carbon .identity .application .common .util .IdentityApplicationManagementUtil ;
56
+ import org .wso2 .carbon .idp .mgt .IdentityProviderManagementException ;
57
+ import org .wso2 .carbon .idp .mgt .IdentityProviderManager ;
48
58
import org .wso2 .carbon .user .api .UserStoreException ;
49
59
import org .wso2 .carbon .user .core .service .RealmService ;
50
60
import org .wso2 .carbon .utils .CarbonUtils ;
51
61
import org .wso2 .carbon .utils .multitenancy .MultitenantConstants ;
52
62
import org .wso2 .carbon .utils .multitenancy .MultitenantUtils ;
53
63
64
+ import javax .security .cert .X509Certificate ;
65
+ import java .io .ByteArrayInputStream ;
66
+ import java .io .IOException ;
67
+ import java .io .InputStream ;
54
68
import java .net .MalformedURLException ;
55
69
import java .net .URL ;
56
70
import java .text .ParseException ;
@@ -66,6 +80,8 @@ public class OAuthJwtAuthenticatorImpl extends AbstractOAuthAuthenticator {
66
80
private static final Log log = LogFactory .getLog (OAuthJwtAuthenticatorImpl .class );
67
81
private static final String SUPER_TENANT_SUFFIX =
68
82
APIConstants .EMAIL_DOMAIN_SEPARATOR + MultitenantConstants .SUPER_TENANT_DOMAIN_NAME ;
83
+ private static final String OIDC_IDP_ENTITY_ID = "IdPEntityId" ;
84
+ public static final String JWKS_URI = "jwksUri" ;
69
85
private boolean isRESTApiTokenCacheEnabled ;
70
86
private Map <String , TokenIssuerDto > tokenIssuers ;
71
87
@@ -226,70 +242,65 @@ private JWTValidationInfo validateJWTToken(SignedJWTInfo signedJWTInfo, String j
226
242
227
243
if (StringUtils .isNotEmpty (issuer )) {
228
244
//validate Issuer
229
- if (tokenIssuers != null && validateIssuer (subject , issuer , maskedToken )) {
230
- if (isRESTApiTokenCacheEnabled ) {
231
- JWTValidationInfo tempJWTValidationInfo = (JWTValidationInfo ) getRESTAPITokenCache ().get (jti );
232
- if (tempJWTValidationInfo != null ) {
233
- boolean isExpired = checkTokenExpiration (new Date (tempJWTValidationInfo .getExpiryTime ()));
234
- if (isExpired ) {
235
- tempJWTValidationInfo .setValid (false );
236
- getRESTAPITokenCache ().remove (jti );
237
- getRESTAPIInvalidTokenCache ().put (jti , tempJWTValidationInfo );
238
- log .error ("JWT token validation failed. Reason: Expired Token. " + maskedToken );
239
- return tempJWTValidationInfo ;
240
- }
241
- //check accessToken
242
- if (!tempJWTValidationInfo .getRawPayload ().equals (accessToken )) {
243
- tempJWTValidationInfo .setValid (false );
244
- getRESTAPITokenCache ().remove (jti );
245
- getRESTAPIInvalidTokenCache ().put (jti , tempJWTValidationInfo );
246
- log .error ("JWT token validation failed. Reason: Invalid Token. " + maskedToken );
247
- return tempJWTValidationInfo ;
248
- }
245
+ JWTValidator jwtValidator ;
246
+ try {
247
+ jwtValidator = validateAndGetJWTValidatorForIssuer (subject , issuer , maskedToken );
248
+ } catch (APIManagementException e ) {
249
+ log .error (e .getMessage (), e );
250
+ return null ;
251
+ }
252
+ if (isRESTApiTokenCacheEnabled ) {
253
+ JWTValidationInfo tempJWTValidationInfo = (JWTValidationInfo ) getRESTAPITokenCache ().get (jti );
254
+ if (tempJWTValidationInfo != null ) {
255
+ boolean isExpired = checkTokenExpiration (new Date (tempJWTValidationInfo .getExpiryTime ()));
256
+ if (isExpired ) {
257
+ tempJWTValidationInfo .setValid (false );
258
+ getRESTAPITokenCache ().remove (jti );
259
+ getRESTAPIInvalidTokenCache ().put (jti , tempJWTValidationInfo );
260
+ log .error ("JWT token validation failed. Reason: Expired Token. " + maskedToken );
249
261
return tempJWTValidationInfo ;
262
+ }
263
+ //check accessToken
264
+ if (!tempJWTValidationInfo .getRawPayload ().equals (accessToken )) {
265
+ tempJWTValidationInfo .setValid (false );
266
+ getRESTAPITokenCache ().remove (jti );
267
+ getRESTAPIInvalidTokenCache ().put (jti , tempJWTValidationInfo );
268
+ log .error ("JWT token validation failed. Reason: Invalid Token. " + maskedToken );
269
+ return tempJWTValidationInfo ;
270
+ }
271
+ return tempJWTValidationInfo ;
250
272
251
- } else if (getRESTAPIInvalidTokenCache ().get (jti ) != null ) {
252
- if (log .isDebugEnabled ()) {
253
- log .debug ("Token retrieved from the invalid token cache. Token: " + maskedToken );
254
- }
255
- return (JWTValidationInfo ) getRESTAPIInvalidTokenCache ().get (jti );
273
+ } else if (getRESTAPIInvalidTokenCache ().get (jti ) != null ) {
274
+ if (log .isDebugEnabled ()) {
275
+ log .debug ("Token retrieved from the invalid token cache. Token: " + maskedToken );
256
276
}
277
+ return (JWTValidationInfo ) getRESTAPIInvalidTokenCache ().get (jti );
257
278
}
258
- //info not in cache. validate signature and exp
259
- JWTValidator jwtValidator = getJWTValidator (issuer , subject , maskedToken );
260
- if (jwtValidator != null ) {
261
- jwtValidationInfo = jwtValidator .validateToken (signedJWTInfo );
262
- if (jwtValidationInfo .isValid ()) {
263
- //valid token
264
- if (isRESTApiTokenCacheEnabled ) {
265
- getRESTAPITokenCache ().put (jti , jwtValidationInfo );
266
- }
267
- } else {
268
- //put in invalid cache
269
- if (isRESTApiTokenCacheEnabled ) {
270
- getRESTAPIInvalidTokenCache ().put (jti , jwtValidationInfo );
271
- }
272
- //invalid credentials : 900901 error code
273
- log .error ("JWT token validation failed. Reason: Invalid Credentials. " +
274
- "Make sure you have provided the correct security credentials in the token :"
275
- + maskedToken );
279
+ }
280
+ //info not in cache. validate signature and exp
281
+ if (jwtValidator != null ) {
282
+ jwtValidationInfo = jwtValidator .validateToken (signedJWTInfo );
283
+ if (jwtValidationInfo .isValid ()) {
284
+ //valid token
285
+ if (isRESTApiTokenCacheEnabled ) {
286
+ getRESTAPITokenCache ().put (jti , jwtValidationInfo );
276
287
}
277
288
} else {
278
- log .error ("JWT token issuer validation failed. Reason: Cannot find a JWTValidator for the " +
279
- "issuer present in the JWT: " + issuer );
280
- return null ;
289
+ //put in invalid cache
290
+ if (isRESTApiTokenCacheEnabled ) {
291
+ getRESTAPIInvalidTokenCache ().put (jti , jwtValidationInfo );
292
+ }
293
+ //invalid credentials : 900901 error code
294
+ log .error ("JWT token validation failed. Reason: Invalid Credentials. " +
295
+ "Make sure you have provided the correct security credentials in the token :"
296
+ + maskedToken );
281
297
}
282
298
} else {
283
- //invalid issuer. invalid token
284
- if (tokenIssuers != null ) {
285
- log .error ("JWT token issuer validation failed. Reason: Issuer present in the JWT (" + issuer
286
- + ") does not match with the token issuer (" + tokenIssuers .keySet () + ")" );
287
- } else {
288
- log .error ("JWT token issuer validation failed. Reason: Issuer present in the JWT (" + issuer
289
- + ") does not match with the token issuer." );
290
- }
299
+ log .error ("JWT token issuer validation failed. Reason: Cannot find a JWTValidator for the " +
300
+ "issuer present in the JWT: " + issuer );
291
301
return null ;
292
302
}
303
+
293
304
} else {
294
305
log .error ("Issuer is not found in the token " + maskedToken );
295
306
return null ;
@@ -315,16 +326,15 @@ private String getOrganizationFromSubject(String subject, String maskedToken) {
315
326
/**
316
327
* Retrieve JWT Validator for the given issuer.
317
328
*
318
- * @param issuer Issuer from the token
319
- * @param subject Subject from the token
320
- * @param maskedToken Masked token string for logging
329
+ * @param issuer Issuer from the token
330
+ * @param organization Organization
331
+ * @param maskedToken Masked token string for logging
321
332
* @return JWTValidator implementation for the given issuer.
322
333
*/
323
- private JWTValidator getJWTValidator (String issuer , String subject , String maskedToken ) {
334
+ private JWTValidator getJWTValidator (String issuer , String organization , String maskedToken ) {
324
335
325
336
JWTValidator jwtValidator = APIMConfigUtil .getJWTValidatorMap ().get (issuer );
326
337
if (jwtValidator == null ) {
327
- String organization = getOrganizationFromSubject (subject , maskedToken );
328
338
if (StringUtils .isNotEmpty (issuer ) && StringUtils .isNotEmpty (organization )) {
329
339
KeyManagerDto keyManagerDto = KeyManagerHolder .getKeyManagerByIssuer (organization , issuer );
330
340
if (keyManagerDto != null && keyManagerDto .getJwtValidator () != null ) {
@@ -341,26 +351,68 @@ private JWTValidator getJWTValidator(String issuer, String subject, String maske
341
351
* @param subject Subject to derive the logged-in organization
342
352
* @param tokenIssuer Token issuer from the token
343
353
* @param maskedToken Masked token for logging purposes
344
- * @return if issuer validation fails or sucess
354
+ * @return if issuer validation fails or success
345
355
* @throws APIManagementException if an error occurs during validation
346
356
*/
347
- private boolean validateIssuer (String subject , String tokenIssuer , String maskedToken )
357
+ private JWTValidator validateAndGetJWTValidatorForIssuer (String subject , String tokenIssuer , String maskedToken )
348
358
throws APIManagementException {
349
359
350
360
String organization = getOrganizationFromSubject (subject , maskedToken );
351
361
if (tokenIssuers != null && !tokenIssuers .isEmpty ()) {
352
- return tokenIssuers .containsKey (tokenIssuer );
362
+ if (tokenIssuers .containsKey (tokenIssuer )) {
363
+ return getJWTValidator (tokenIssuer , organization , maskedToken );
364
+ }
365
+ throw new APIManagementException ("JWT token issuer validation failed. Reason: Issuer present in the JWT ("
366
+ + tokenIssuer + ") does not match with the token issuer (" + tokenIssuers .keySet () + ")" );
353
367
}
354
- KeyManagerConfigurationDTO keyManagerConfigurationDTO =
355
- APIUtil .getDefaultKeyManagerConfiguration (organization );
356
- if (keyManagerConfigurationDTO == null ) {
368
+ IdentityProvider residentIDP = validateAndGetResidentIDPForIssuer (organization , tokenIssuer );
369
+ if (residentIDP == null ) {
357
370
//invalid issuer. invalid token
358
- log .error ("JWT token issuer validation failed. Reason: Default Key Manager configuration cannot be"
359
- + " found for the organization: " + organization );
360
- return false ;
371
+ throw new APIManagementException ("JWT token issuer validation failed. Reason: Resident Identity Provider "
372
+ + "cannot be found for the organization: " + organization );
373
+ }
374
+ JWTValidator jwtValidator = new JWTValidatorImpl ();
375
+ TokenIssuerDto tokenIssuerDto = new TokenIssuerDto (tokenIssuer );
376
+ if (residentIDP .getCertificate () != null ) {
377
+ tokenIssuerDto .setCertificate (retrieveCertificateFromContent (residentIDP .getCertificate ()));
378
+ } else {
379
+ JWKSConfigurationDTO jwksConfigurationDTO = new JWKSConfigurationDTO ();
380
+ jwksConfigurationDTO .setEnabled (true );
381
+ jwksConfigurationDTO .setUrl (getJwksUriForIDP (residentIDP ));
382
+ tokenIssuerDto .setJwksConfigurationDTO (jwksConfigurationDTO );
383
+ }
384
+ jwtValidator .loadTokenIssuerConfiguration (tokenIssuerDto );
385
+ return jwtValidator ;
386
+ }
387
+
388
+ /**
389
+ * Retrieve JWKS URI configured for the resident IDP.
390
+ *
391
+ * @param idp IdentityProvider
392
+ * @return JWKS URI
393
+ */
394
+ private String getJwksUriForIDP (IdentityProvider idp ) {
395
+
396
+ String jwksUri = null ;
397
+ IdentityProviderProperty [] identityProviderProperties = idp .getIdpProperties ();
398
+ if (!ArrayUtils .isEmpty (identityProviderProperties )) {
399
+ for (IdentityProviderProperty identityProviderProperty : identityProviderProperties ) {
400
+ if (StringUtils .equals (identityProviderProperty .getName (), JWKS_URI )) {
401
+ jwksUri = identityProviderProperty .getValue ();
402
+ if (log .isDebugEnabled ()) {
403
+ log .debug ("JWKS endpoint set for the identity provider : " + idp .getIdentityProviderName ()
404
+ + ", jwks_uri : " + jwksUri );
405
+ }
406
+ break ;
407
+ } else {
408
+ if (log .isDebugEnabled ()) {
409
+ log .debug ("JWKS endpoint not specified for the identity provider : "
410
+ + idp .getIdentityProviderName ());
411
+ }
412
+ }
413
+ }
361
414
}
362
- return StringUtils .equals (tokenIssuer , (String ) keyManagerConfigurationDTO .getAdditionalProperties ()
363
- .get (APIConstants .KeyManager .ISSUER ));
415
+ return jwksUri ;
364
416
}
365
417
366
418
/**
@@ -398,4 +450,60 @@ private String getJWTTokenIdentifier(SignedJWTInfo signedJWTInfo) {
398
450
}
399
451
return signedJWTInfo .getSignedJWT ().getSignature ().toString ();
400
452
}
453
+
454
+
455
+ /**
456
+ * Validate issuer and get resident Identity Provider.
457
+ *
458
+ * @param tenantDomain tenant Domain
459
+ * @param jwtIssuer issuer extracted from assertion
460
+ * @return resident Identity Provider
461
+ * @throws APIManagementException if an error occurs while retrieving resident idp issuer
462
+ */
463
+ private IdentityProvider validateAndGetResidentIDPForIssuer (String tenantDomain , String jwtIssuer )
464
+ throws APIManagementException {
465
+
466
+ String issuer = StringUtils .EMPTY ;
467
+ IdentityProvider residentIdentityProvider ;
468
+ try {
469
+ residentIdentityProvider = IdentityProviderManager .getInstance ().getResidentIdP (tenantDomain );
470
+ } catch (IdentityProviderManagementException e ) {
471
+ String errorMsg = String .format ("Error while getting Resident Identity Provider of '%s' tenant." ,
472
+ tenantDomain );
473
+ throw new APIManagementException (errorMsg , e );
474
+ }
475
+ FederatedAuthenticatorConfig [] fedAuthnConfigs = residentIdentityProvider .getFederatedAuthenticatorConfigs ();
476
+ FederatedAuthenticatorConfig oauthAuthenticatorConfig =
477
+ IdentityApplicationManagementUtil .getFederatedAuthenticator (fedAuthnConfigs ,
478
+ IdentityApplicationConstants .Authenticator .OIDC .NAME );
479
+ if (oauthAuthenticatorConfig != null ) {
480
+ issuer = IdentityApplicationManagementUtil .getProperty (oauthAuthenticatorConfig .getProperties (),
481
+ OIDC_IDP_ENTITY_ID ).getValue ();
482
+ }
483
+ return jwtIssuer .equals (issuer ) ? residentIdentityProvider : null ;
484
+ }
485
+
486
+ /**
487
+ * Util method to convert base64 encoded certificate content to X509Certificate instance.
488
+ *
489
+ * @param base64EncodedCertificate Base64 encoded cert string (not URL encoded)
490
+ * @return javax.security.cert.X509Certificate
491
+ * @throws APIManagementException if an error occurs while retrieving from IDP
492
+ */
493
+ private X509Certificate retrieveCertificateFromContent (String base64EncodedCertificate )
494
+ throws APIManagementException {
495
+
496
+ if (base64EncodedCertificate != null ) {
497
+ base64EncodedCertificate = APIUtil .getX509certificateContent (base64EncodedCertificate );
498
+ byte [] bytes = Base64 .decodeBase64 (base64EncodedCertificate .getBytes ());
499
+ try (InputStream inputStream = new ByteArrayInputStream (bytes )) {
500
+ return X509Certificate .getInstance (inputStream );
501
+ } catch (IOException | javax .security .cert .CertificateException e ) {
502
+ String msg = "Error while converting into X509Certificate" ;
503
+ log .error (msg , e );
504
+ throw new APIManagementException (msg , e );
505
+ }
506
+ }
507
+ return null ;
508
+ }
401
509
}
0 commit comments