added support for encrypted and symmetrically signed id tokens and user info responses

pull/604/head
Justin Richer 2014-05-23 21:15:50 -04:00
parent ffe1b29906
commit 05e9624ae3
5 changed files with 134 additions and 30 deletions

View File

@ -45,8 +45,7 @@ public interface OIDCTokenService {
*/
public OAuth2AccessTokenEntity createIdToken(
ClientDetailsEntity client, OAuth2Request request, Date issueTime,
String sub, JWSAlgorithm signingAlg,
OAuth2AccessTokenEntity accessToken);
String sub, OAuth2AccessTokenEntity accessToken);
/**
* Create a registration access token for the given client.

View File

@ -17,9 +17,7 @@
package org.mitre.discovery.web;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.mitre.discovery.util.WebfingerURLNormalizer;
@ -278,12 +276,12 @@ public class DiscoveryEndpoint {
m.put("grant_types_supported", Lists.newArrayList("authorization_code", "implicit", "urn:ietf:params:oauth:grant-type:jwt-bearer", "client_credentials", "urn:ietf:params:oauth:grant_type:redelegate"));
//acr_values_supported
m.put("subject_types_supported", Lists.newArrayList("public", "pairwise"));
//userinfo_signing_alg_values_supported
//userinfo_encryption_alg_values_supported
//userinfo_encryption_enc_values_supported
m.put("id_token_signing_alg_values_supported", Collections2.transform(serverSigningAlgs, toAlgorithmName));
//id_token_encryption_alg_values_supported
//id_token_encryption_enc_values_supported
m.put("userinfo_signing_alg_values_supported", Collections2.transform(clientSymmetricAndAsymmetricSigningAlgs, toAlgorithmName));
m.put("userinfo_encryption_alg_values_supported", Collections2.transform(encService.getAllEncryptionAlgsSupported(), toAlgorithmName));
m.put("userinfo_encryption_enc_values_supported", Collections2.transform(encService.getAllEncryptionEncsSupported(), toAlgorithmName));
m.put("id_token_signing_alg_values_supported", Collections2.transform(clientSymmetricAndAsymmetricSigningAlgs, toAlgorithmName));
m.put("id_token_encryption_alg_values_supported", Collections2.transform(encService.getAllEncryptionAlgsSupported(), toAlgorithmName));
m.put("id_token_encryption_enc_values_supported", Collections2.transform(encService.getAllEncryptionEncsSupported(), toAlgorithmName));
m.put("request_object_signing_alg_values_supported", Collections2.transform(clientSymmetricAndAsymmetricSigningAlgs, toAlgorithmName));
m.put("request_object_encryption_alg_values_supported", Collections2.transform(encService.getAllEncryptionAlgsSupported(), toAlgorithmName));
m.put("request_object_encryption_enc_values_supported", Collections2.transform(encService.getAllEncryptionEncsSupported(), toAlgorithmName));

View File

@ -21,7 +21,10 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.mitre.jwt.encryption.service.JwtEncryptionAndDecryptionService;
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
import org.mitre.jwt.signer.service.impl.SymmetricCacheService;
import org.mitre.oauth2.model.AuthenticationHolderEntity;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
@ -44,9 +47,12 @@ import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.nimbusds.jose.Algorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jwt.EncryptedJWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
@ -70,8 +76,21 @@ public class DefaultOIDCTokenService implements OIDCTokenService {
@Autowired
private ConfigurationPropertiesBean configBean;
@Autowired
private JWKSetCacheService encrypters;
@Autowired
private SymmetricCacheService symmetricCacheService;
@Override
public OAuth2AccessTokenEntity createIdToken(ClientDetailsEntity client, OAuth2Request request, Date issueTime, String sub, JWSAlgorithm signingAlg, OAuth2AccessTokenEntity accessToken) {
public OAuth2AccessTokenEntity createIdToken(ClientDetailsEntity client, OAuth2Request request, Date issueTime, String sub, OAuth2AccessTokenEntity accessToken) {
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
if (client.getIdTokenSignedResponseAlg() != null) {
signingAlg = client.getIdTokenSignedResponseAlg();
}
OAuth2AccessTokenEntity idTokenEntity = new OAuth2AccessTokenEntity();
JWTClaimsSet idClaims = new JWTClaimsSet();
@ -105,12 +124,44 @@ public class DefaultOIDCTokenService implements OIDCTokenService {
Base64URL at_hash = IdTokenHashUtils.getAccessTokenHash(signingAlg, accessToken);
idClaims.setClaim("at_hash", at_hash);
}
if (client.getIdTokenEncryptedResponseAlg() != null && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE)
&& client.getIdTokenEncryptedResponseEnc() != null && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE)
&& !Strings.isNullOrEmpty(client.getJwksUri())) {
JwtEncryptionAndDecryptionService encrypter = encrypters.getEncrypter(client.getJwksUri());
if (encrypter != null) {
EncryptedJWT idToken = new EncryptedJWT(new JWEHeader(client.getIdTokenEncryptedResponseAlg(), client.getIdTokenEncryptedResponseEnc()), idClaims);
encrypter.encryptJwt(idToken);
idTokenEntity.setJwt(idToken);
} else {
logger.error("Couldn't find encrypter for client: " + client.getClientId());
}
} else {
SignedJWT idToken = new SignedJWT(new JWSHeader(signingAlg), idClaims);
jwtService.signJwt(idToken);
idTokenEntity.setJwt(idToken);
SignedJWT idToken = new SignedJWT(new JWSHeader(signingAlg), idClaims);
if (signingAlg.equals(JWSAlgorithm.HS256)
|| signingAlg.equals(JWSAlgorithm.HS384)
|| signingAlg.equals(JWSAlgorithm.HS512)) {
JwtSigningAndValidationService signer = symmetricCacheService.getSymmetricValidtor(client);
// sign it with the client's secret
signer.signJwt(idToken);
} else {
// sign it with the server's key
jwtService.signJwt(idToken);
}
idTokenEntity.setJwt(idToken);
}
idTokenEntity.setAuthenticationHolder(accessToken.getAuthenticationHolder());

View File

@ -20,6 +20,8 @@ import java.util.Date;
import java.util.UUID;
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
import org.mitre.jwt.signer.service.impl.SymmetricCacheService;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
import org.mitre.oauth2.service.ClientDetailsEntityService;
@ -39,6 +41,7 @@ import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.stereotype.Service;
import com.google.common.collect.Lists;
import com.nimbusds.jose.Algorithm;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jwt.JWTClaimsSet;
@ -67,6 +70,13 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
@Autowired
private OIDCTokenService connectTokenService;
@Autowired
private JWKSetCacheService encryptors;
@Autowired
private SymmetricCacheService symmetricCacheService;
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
@ -89,16 +99,13 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
claims.setJWTID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
if (client.getIdTokenSignedResponseAlg() != null) {
signingAlg = client.getIdTokenSignedResponseAlg();
}
SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims);
jwtService.signJwt(signed);
token.setJwt(signed);
/**
* Authorization request scope MUST include "openid" in OIDC, but access token request
* may or may not include the scope parameter. As long as the AuthorizationRequest
@ -118,7 +125,7 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
OAuth2AccessTokenEntity idTokenEntity = connectTokenService.createIdToken(client,
originalAuthRequest, claims.getIssueTime(),
userInfo.getSub(), signingAlg, token);
userInfo.getSub(), token);
// attach the id token to the parent access token
token.setIdToken(idTokenEntity);

View File

@ -15,7 +15,10 @@ import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.mitre.jwt.encryption.service.JwtEncryptionAndDecryptionService;
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
import org.mitre.jwt.signer.service.impl.SymmetricCacheService;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
import org.slf4j.Logger;
@ -23,11 +26,15 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.nimbusds.jose.Algorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jwt.EncryptedJWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
@ -46,6 +53,12 @@ public class UserInfoJwtView extends UserInfoView {
@Autowired
private ConfigurationPropertiesBean config;
@Autowired
private JWKSetCacheService encrypters;
@Autowired
private SymmetricCacheService symmetricCacheService;
@Override
protected void writeOut(JsonObject json, Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) {
@ -67,17 +80,53 @@ public class UserInfoJwtView extends UserInfoView {
claims.setJWTID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
if (client.getUserInfoSignedResponseAlg() != null) {
signingAlg = client.getUserInfoSignedResponseAlg();
}
SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims);
jwtService.signJwt(signed);
if (client.getIdTokenEncryptedResponseAlg() != null && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE)
&& client.getIdTokenEncryptedResponseEnc() != null && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE)
&& !Strings.isNullOrEmpty(client.getJwksUri())) {
// encrypt it to the client's key
JwtEncryptionAndDecryptionService encrypter = encrypters.getEncrypter(client.getJwksUri());
if (encrypter != null) {
EncryptedJWT encrypted = new EncryptedJWT(new JWEHeader(client.getIdTokenEncryptedResponseAlg(), client.getIdTokenEncryptedResponseEnc()), claims);
encrypter.encryptJwt(encrypted);
Writer out = response.getWriter();
out.write(encrypted.serialize());
} else {
logger.error("Couldn't find encrypter for client: " + client.getClientId());
}
} else {
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
if (client.getUserInfoSignedResponseAlg() != null) {
signingAlg = client.getUserInfoSignedResponseAlg();
}
SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims);
Writer out = response.getWriter();
out.write(signed.serialize());
if (signingAlg.equals(JWSAlgorithm.HS256)
|| signingAlg.equals(JWSAlgorithm.HS384)
|| signingAlg.equals(JWSAlgorithm.HS512)) {
// sign it with the client's secret
JwtSigningAndValidationService signer = symmetricCacheService.getSymmetricValidtor(client);
signer.signJwt(signed);
} else {
// sign it with the server's key
jwtService.signJwt(signed);
}
Writer out = response.getWriter();
out.write(signed.serialize());
}
} catch (IOException e) {
logger.error("IO Exception in UserInfoJwtView", e);
} catch (ParseException e) {