001/******************************************************************************* 002 * Copyright 2017 The MIT Internet Trust Consortium 003 * 004 * Portions copyright 2011-2013 The MITRE Corporation 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 *******************************************************************************/ 018package org.mitre.openid.connect.service.impl; 019 020import static org.mitre.openid.connect.request.ConnectRequestParameters.MAX_AGE; 021import static org.mitre.openid.connect.request.ConnectRequestParameters.NONCE; 022 023import java.util.Date; 024import java.util.Map; 025import java.util.Set; 026import java.util.UUID; 027 028import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService; 029import org.mitre.jwt.signer.service.JWTSigningAndValidationService; 030import org.mitre.jwt.signer.service.impl.ClientKeyCacheService; 031import org.mitre.jwt.signer.service.impl.SymmetricKeyJWTValidatorCacheService; 032import org.mitre.oauth2.model.AuthenticationHolderEntity; 033import org.mitre.oauth2.model.ClientDetailsEntity; 034import org.mitre.oauth2.model.OAuth2AccessTokenEntity; 035import org.mitre.oauth2.repository.AuthenticationHolderRepository; 036import org.mitre.oauth2.service.OAuth2TokenEntityService; 037import org.mitre.oauth2.service.SystemScopeService; 038import org.mitre.openid.connect.config.ConfigurationPropertiesBean; 039import org.mitre.openid.connect.service.OIDCTokenService; 040import org.mitre.openid.connect.util.IdTokenHashUtils; 041import org.mitre.openid.connect.web.AuthenticationTimeStamper; 042import org.slf4j.Logger; 043import org.slf4j.LoggerFactory; 044import org.springframework.beans.factory.annotation.Autowired; 045import org.springframework.security.core.AuthenticationException; 046import org.springframework.security.core.authority.SimpleGrantedAuthority; 047import org.springframework.security.oauth2.provider.OAuth2Authentication; 048import org.springframework.security.oauth2.provider.OAuth2Request; 049import org.springframework.stereotype.Service; 050 051import com.google.common.base.Strings; 052import com.google.common.collect.Lists; 053import com.google.common.collect.Maps; 054import com.google.common.collect.Sets; 055import com.nimbusds.jose.Algorithm; 056import com.nimbusds.jose.JWEHeader; 057import com.nimbusds.jose.JWEObject; 058import com.nimbusds.jose.JWSAlgorithm; 059import com.nimbusds.jose.JWSHeader; 060import com.nimbusds.jose.util.Base64URL; 061import com.nimbusds.jwt.EncryptedJWT; 062import com.nimbusds.jwt.JWT; 063import com.nimbusds.jwt.JWTClaimsSet; 064import com.nimbusds.jwt.PlainJWT; 065import com.nimbusds.jwt.SignedJWT; 066/** 067 * Default implementation of service to create specialty OpenID Connect tokens. 068 * 069 * @author Amanda Anganes 070 * 071 */ 072@Service 073public class DefaultOIDCTokenService implements OIDCTokenService { 074 075 /** 076 * Logger for this class 077 */ 078 private static final Logger logger = LoggerFactory.getLogger(DefaultOIDCTokenService.class); 079 080 @Autowired 081 private JWTSigningAndValidationService jwtService; 082 083 @Autowired 084 private AuthenticationHolderRepository authenticationHolderRepository; 085 086 @Autowired 087 private ConfigurationPropertiesBean configBean; 088 089 @Autowired 090 private ClientKeyCacheService encrypters; 091 092 @Autowired 093 private SymmetricKeyJWTValidatorCacheService symmetricCacheService; 094 095 @Autowired 096 private OAuth2TokenEntityService tokenService; 097 098 @Override 099 public JWT createIdToken(ClientDetailsEntity client, OAuth2Request request, Date issueTime, String sub, OAuth2AccessTokenEntity accessToken) { 100 101 JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm(); 102 103 if (client.getIdTokenSignedResponseAlg() != null) { 104 signingAlg = client.getIdTokenSignedResponseAlg(); 105 } 106 107 108 JWT idToken = null; 109 110 JWTClaimsSet.Builder idClaims = new JWTClaimsSet.Builder(); 111 112 // if the auth time claim was explicitly requested OR if the client always wants the auth time, put it in 113 if (request.getExtensions().containsKey(MAX_AGE) 114 || (request.getExtensions().containsKey("idtoken")) // TODO: parse the ID Token claims (#473) -- for now assume it could be in there 115 || (client.getRequireAuthTime() != null && client.getRequireAuthTime())) { 116 117 if (request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP) != null) { 118 119 Long authTimestamp = Long.parseLong((String) request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP)); 120 if (authTimestamp != null) { 121 idClaims.claim("auth_time", authTimestamp / 1000L); 122 } 123 } else { 124 // we couldn't find the timestamp! 125 logger.warn("Unable to find authentication timestamp! There is likely something wrong with the configuration."); 126 } 127 } 128 129 idClaims.issueTime(issueTime); 130 131 if (client.getIdTokenValiditySeconds() != null) { 132 Date expiration = new Date(System.currentTimeMillis() + (client.getIdTokenValiditySeconds() * 1000L)); 133 idClaims.expirationTime(expiration); 134 } 135 136 idClaims.issuer(configBean.getIssuer()); 137 idClaims.subject(sub); 138 idClaims.audience(Lists.newArrayList(client.getClientId())); 139 idClaims.jwtID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it 140 141 String nonce = (String)request.getExtensions().get(NONCE); 142 if (!Strings.isNullOrEmpty(nonce)) { 143 idClaims.claim("nonce", nonce); 144 } 145 146 Set<String> responseTypes = request.getResponseTypes(); 147 148 if (responseTypes.contains("token")) { 149 // calculate the token hash 150 Base64URL at_hash = IdTokenHashUtils.getAccessTokenHash(signingAlg, accessToken); 151 idClaims.claim("at_hash", at_hash); 152 } 153 154 addCustomIdTokenClaims(idClaims, client, request, sub, accessToken); 155 156 if (client.getIdTokenEncryptedResponseAlg() != null && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE) 157 && client.getIdTokenEncryptedResponseEnc() != null && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE) 158 && (!Strings.isNullOrEmpty(client.getJwksUri()) || client.getJwks() != null)) { 159 160 JWTEncryptionAndDecryptionService encrypter = encrypters.getEncrypter(client); 161 162 if (encrypter != null) { 163 164 idToken = new EncryptedJWT(new JWEHeader(client.getIdTokenEncryptedResponseAlg(), client.getIdTokenEncryptedResponseEnc()), idClaims.build()); 165 166 encrypter.encryptJwt((JWEObject) idToken); 167 168 } else { 169 logger.error("Couldn't find encrypter for client: " + client.getClientId()); 170 } 171 172 } else { 173 174 if (signingAlg.equals(Algorithm.NONE)) { 175 // unsigned ID token 176 idToken = new PlainJWT(idClaims.build()); 177 178 } else { 179 180 // signed ID token 181 182 if (signingAlg.equals(JWSAlgorithm.HS256) 183 || signingAlg.equals(JWSAlgorithm.HS384) 184 || signingAlg.equals(JWSAlgorithm.HS512)) { 185 186 JWSHeader header = new JWSHeader(signingAlg, null, null, null, null, null, null, null, null, null, 187 jwtService.getDefaultSignerKeyId(), 188 null, null); 189 idToken = new SignedJWT(header, idClaims.build()); 190 191 JWTSigningAndValidationService signer = symmetricCacheService.getSymmetricValidtor(client); 192 193 // sign it with the client's secret 194 signer.signJwt((SignedJWT) idToken); 195 } else { 196 idClaims.claim("kid", jwtService.getDefaultSignerKeyId()); 197 198 JWSHeader header = new JWSHeader(signingAlg, null, null, null, null, null, null, null, null, null, 199 jwtService.getDefaultSignerKeyId(), 200 null, null); 201 202 idToken = new SignedJWT(header, idClaims.build()); 203 204 // sign it with the server's key 205 jwtService.signJwt((SignedJWT) idToken); 206 } 207 } 208 209 } 210 211 return idToken; 212 } 213 214 /** 215 * @param client 216 * @return 217 * @throws AuthenticationException 218 */ 219 @Override 220 public OAuth2AccessTokenEntity createRegistrationAccessToken(ClientDetailsEntity client) { 221 222 return createAssociatedToken(client, Sets.newHashSet(SystemScopeService.REGISTRATION_TOKEN_SCOPE)); 223 224 } 225 226 /** 227 * @param client 228 * @return 229 */ 230 @Override 231 public OAuth2AccessTokenEntity createResourceAccessToken(ClientDetailsEntity client) { 232 233 return createAssociatedToken(client, Sets.newHashSet(SystemScopeService.RESOURCE_TOKEN_SCOPE)); 234 235 } 236 237 @Override 238 public OAuth2AccessTokenEntity rotateRegistrationAccessTokenForClient(ClientDetailsEntity client) { 239 // revoke any previous tokens 240 OAuth2AccessTokenEntity oldToken = tokenService.getRegistrationAccessTokenForClient(client); 241 if (oldToken != null) { 242 Set<String> scope = oldToken.getScope(); 243 tokenService.revokeAccessToken(oldToken); 244 return createAssociatedToken(client, scope); 245 } else { 246 return null; 247 } 248 249 } 250 251 private OAuth2AccessTokenEntity createAssociatedToken(ClientDetailsEntity client, Set<String> scope) { 252 253 // revoke any previous tokens that might exist, just to be sure 254 OAuth2AccessTokenEntity oldToken = tokenService.getRegistrationAccessTokenForClient(client); 255 if (oldToken != null) { 256 tokenService.revokeAccessToken(oldToken); 257 } 258 259 // create a new token 260 261 Map<String, String> authorizationParameters = Maps.newHashMap(); 262 OAuth2Request clientAuth = new OAuth2Request(authorizationParameters, client.getClientId(), 263 Sets.newHashSet(new SimpleGrantedAuthority("ROLE_CLIENT")), true, 264 scope, null, null, null, null); 265 OAuth2Authentication authentication = new OAuth2Authentication(clientAuth, null); 266 267 OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity(); 268 token.setClient(client); 269 token.setScope(scope); 270 271 AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(); 272 authHolder.setAuthentication(authentication); 273 authHolder = authenticationHolderRepository.save(authHolder); 274 token.setAuthenticationHolder(authHolder); 275 276 JWTClaimsSet claims = new JWTClaimsSet.Builder() 277 .audience(Lists.newArrayList(client.getClientId())) 278 .issuer(configBean.getIssuer()) 279 .issueTime(new Date()) 280 .expirationTime(token.getExpiration()) 281 .jwtID(UUID.randomUUID().toString()) // set a random NONCE in the middle of it 282 .build(); 283 284 JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm(); 285 JWSHeader header = new JWSHeader(signingAlg, null, null, null, null, null, null, null, null, null, 286 jwtService.getDefaultSignerKeyId(), 287 null, null); 288 SignedJWT signed = new SignedJWT(header, claims); 289 290 jwtService.signJwt(signed); 291 292 token.setJwt(signed); 293 294 return token; 295 } 296 297 /** 298 * @return the configBean 299 */ 300 public ConfigurationPropertiesBean getConfigBean() { 301 return configBean; 302 } 303 304 /** 305 * @param configBean the configBean to set 306 */ 307 public void setConfigBean(ConfigurationPropertiesBean configBean) { 308 this.configBean = configBean; 309 } 310 311 /** 312 * @return the jwtService 313 */ 314 public JWTSigningAndValidationService getJwtService() { 315 return jwtService; 316 } 317 318 /** 319 * @param jwtService the jwtService to set 320 */ 321 public void setJwtService(JWTSigningAndValidationService jwtService) { 322 this.jwtService = jwtService; 323 } 324 325 /** 326 * @return the authenticationHolderRepository 327 */ 328 public AuthenticationHolderRepository getAuthenticationHolderRepository() { 329 return authenticationHolderRepository; 330 } 331 332 /** 333 * @param authenticationHolderRepository the authenticationHolderRepository to set 334 */ 335 public void setAuthenticationHolderRepository( 336 AuthenticationHolderRepository authenticationHolderRepository) { 337 this.authenticationHolderRepository = authenticationHolderRepository; 338 } 339 340 /** 341 * Hook for subclasses that allows adding custom claims to the JWT 342 * that will be used as id token. 343 * @param idClaims the builder holding the current claims 344 * @param client information about the requesting client 345 * @param request request that caused the id token to be created 346 * @param sub subject auf the id token 347 * @param accessToken the access token 348 * @param authentication current authentication 349 */ 350 protected void addCustomIdTokenClaims(JWTClaimsSet.Builder idClaims, ClientDetailsEntity client, OAuth2Request request, 351 String sub, OAuth2AccessTokenEntity accessToken) { 352 } 353 354}