/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.cxf.rs.security.oidc.utils; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.core.MultivaluedMap; import org.apache.cxf.common.util.Base64UrlUtility; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; import org.apache.cxf.rs.security.jose.jws.JwsException; import org.apache.cxf.rs.security.jose.jwt.JwtToken; import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken; import org.apache.cxf.rs.security.oauth2.common.OAuthRedirectionState; import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException; import org.apache.cxf.rs.security.oidc.common.IdToken; import org.apache.cxf.rs.security.oidc.common.UserInfo; import org.apache.cxf.rt.security.crypto.MessageDigestUtils; public final class OidcUtils { public static final String ID_TOKEN_RESPONSE_TYPE = "id_token"; public static final String ID_TOKEN_AT_RESPONSE_TYPE = "id_token token"; public static final String CODE_AT_RESPONSE_TYPE = "code token"; public static final String CODE_ID_TOKEN_RESPONSE_TYPE = "code id_token"; public static final String CODE_ID_TOKEN_AT_RESPONSE_TYPE = "code id_token token"; public static final String ID_TOKEN = "id_token"; public static final String OPENID_SCOPE = "openid"; public static final String PROFILE_SCOPE = "profile"; public static final String EMAIL_SCOPE = "email"; public static final String ADDRESS_SCOPE = "address"; public static final String PHONE_SCOPE = "phone"; public static final List<String> PROFILE_CLAIMS = Arrays.asList(UserInfo.NAME_CLAIM, UserInfo.PROFILE_CLAIM); public static final List<String> EMAIL_CLAIMS = Arrays.asList(UserInfo.EMAIL_CLAIM, UserInfo.EMAIL_VERIFIED_CLAIM); public static final List<String> ADDRESS_CLAIMS = Arrays.asList(UserInfo.ADDRESS_CLAIM); public static final List<String> PHONE_CLAIMS = Arrays.asList(UserInfo.PHONE_CLAIM); public static final String CLAIMS_PARAM = "claims"; public static final String CLAIM_NAMES_PROPERTY = "_claim_names"; public static final String CLAIM_SOURCES_PROPERTY = "_claim_sources"; public static final String JWT_CLAIM_SOURCE_PROPERTY = "JWT"; public static final String ENDPOINT_CLAIM_SOURCE_PROPERTY = "endpoint"; public static final String TOKEN_CLAIM_SOURCE_PROPERTY = "access_token"; public static final String PROMPT_PARAMETER = "prompt"; public static final String PROMPT_NONE_VALUE = "none"; public static final String PROMPT_CONSENT_VALUE = "consent"; public static final String CONSENT_REQUIRED_ERROR = "consent_required"; private static final Map<String, List<String>> SCOPES_MAP; static { SCOPES_MAP = new HashMap<>(); SCOPES_MAP.put(PHONE_SCOPE, PHONE_CLAIMS); SCOPES_MAP.put(EMAIL_SCOPE, EMAIL_CLAIMS); SCOPES_MAP.put(ADDRESS_SCOPE, ADDRESS_CLAIMS); SCOPES_MAP.put(PROFILE_SCOPE, PROFILE_CLAIMS); } private OidcUtils() { } public static List<String> getPromptValues(MultivaluedMap<String, String> params) { String prompt = params.getFirst(PROMPT_PARAMETER); if (prompt != null) { return Arrays.asList(prompt.trim().split(" ")); } else { return Collections.emptyList(); } } public static String getOpenIdScope() { return OPENID_SCOPE; } public static String getProfileScope() { return getScope(OPENID_SCOPE, PROFILE_SCOPE); } public static String getEmailScope() { return getScope(OPENID_SCOPE, EMAIL_SCOPE); } public static String getAddressScope() { return getScope(OPENID_SCOPE, ADDRESS_SCOPE); } public static String getPhoneScope() { return getScope(OPENID_SCOPE, PHONE_SCOPE); } public static String getAllScopes() { return getScope(OPENID_SCOPE, PROFILE_SCOPE, EMAIL_SCOPE, ADDRESS_SCOPE, PHONE_SCOPE); } public static List<String> getScopeProperties(String scope) { return SCOPES_MAP.get(scope); } private static String getScope(String... scopes) { StringBuilder sb = new StringBuilder(); for (String scope : scopes) { if (sb.length() > 0) { sb.append(" "); } sb.append(scope); } return sb.toString(); } public static void validateAccessTokenHash(ClientAccessToken at, JwtToken jwt) { validateAccessTokenHash(at, jwt, true); } public static void validateAccessTokenHash(ClientAccessToken at, JwtToken jwt, boolean required) { validateAccessTokenHash(at.getTokenKey(), jwt, required); } public static void validateAccessTokenHash(String accessToken, JwtToken jwt, boolean required) { if (required) { validateHash(accessToken, (String)jwt.getClaims().getClaim(IdToken.ACCESS_TOKEN_HASH_CLAIM), jwt.getJwsHeaders().getSignatureAlgorithm()); } } public static void validateCodeHash(String code, JwtToken jwt) { validateCodeHash(code, jwt, true); } public static void validateCodeHash(String code, JwtToken jwt, boolean required) { if (required) { validateHash(code, (String)jwt.getClaims().getClaim(IdToken.AUTH_CODE_HASH_CLAIM), jwt.getJwsHeaders().getSignatureAlgorithm()); } } private static void validateHash(String value, String theHash, SignatureAlgorithm joseAlgo) { String hash = calculateHash(value, joseAlgo); if (!hash.equals(theHash)) { throw new OAuthServiceException("Invalid hash"); } } public static String calculateAccessTokenHash(String value, SignatureAlgorithm sigAlgo) { return calculateHash(value, sigAlgo); } public static String calculateAuthorizationCodeHash(String value, SignatureAlgorithm sigAlgo) { return calculateHash(value, sigAlgo); } private static String calculateHash(String value, SignatureAlgorithm sigAlgo) { if (sigAlgo == SignatureAlgorithm.NONE) { throw new JwsException(JwsException.Error.INVALID_ALGORITHM); } String algoShaSizeString = sigAlgo.getJwaName().substring(2); String javaShaAlgo = "SHA-" + algoShaSizeString; int algoShaSize = Integer.valueOf(algoShaSizeString); int valueHashSize = (algoShaSize / 8) / 2; try { byte[] atBytes = StringUtils.toBytesASCII(value); byte[] digest = MessageDigestUtils.createDigest(atBytes, javaShaAlgo); return Base64UrlUtility.encodeChunk(digest, 0, valueHashSize); } catch (NoSuchAlgorithmException ex) { throw new OAuthServiceException(ex); } } public static void setStateClaimsProperty(OAuthRedirectionState state, MultivaluedMap<String, String> params) { String claims = params.getFirst(OidcUtils.CLAIMS_PARAM); if (claims != null) { state.getExtraProperties().put(OidcUtils.CLAIMS_PARAM, claims); } } }