001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * 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 distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.oauth2.sdk.jarm; 019 020 021import java.util.*; 022 023import com.nimbusds.jwt.*; 024import com.nimbusds.oauth2.sdk.AuthorizationResponse; 025import com.nimbusds.oauth2.sdk.ParseException; 026import com.nimbusds.oauth2.sdk.id.ClientID; 027import com.nimbusds.oauth2.sdk.id.Issuer; 028import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 029 030 031/** 032 * JWT Secured Authorization Response Mode for OAuth 2.0 (JARM) utilities. 033 */ 034public final class JARMUtils { 035 036 037 /** 038 * Creates a JSON Web Token (JWT) claims set for the specified 039 * authorisation success response. 040 * 041 * @param iss The OAuth 2.0 authorisation server issuer. Must not 042 * be {@code null}. 043 * @param aud The client ID. Must not be {@code null}. 044 * @param exp The JWT expiration time. Must not be {@code null}. 045 * @param response The plain authorisation response to use its 046 * parameters. Must not be {@code null}. 047 * 048 * @return The JWT claims set. 049 */ 050 public static JWTClaimsSet toJWTClaimsSet(final Issuer iss, 051 final ClientID aud, 052 final Date exp, 053 final AuthorizationResponse response) { 054 055 if (exp == null) { 056 throw new IllegalArgumentException("The expiration time must not be null"); 057 } 058 059 JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder() 060 .issuer(iss.getValue()) 061 .audience(aud.getValue()) 062 .expirationTime(exp); 063 064 for (Map.Entry<String, ?> en: MultivaluedMapUtils.toSingleValuedMap(response.toParameters()).entrySet()) { 065 066 if ("response".equals(en.getKey())) { 067 continue; // own JARM parameter, skip 068 } 069 070 builder = builder.claim(en.getKey(), en.getValue() + ""); // force string 071 } 072 073 return builder.build(); 074 } 075 076 077 /** 078 * Returns a multi-valued map representation of the specified JWT 079 * claims set. 080 * 081 * @param jwtClaimsSet The JWT claims set. Must not be {@code null}. 082 * 083 * @return The multi-valued map. 084 */ 085 public static Map<String,List<String>> toMultiValuedStringParameters(final JWTClaimsSet jwtClaimsSet) { 086 087 Map<String,List<String>> params = new HashMap<>(); 088 089 for (Map.Entry<String,Object> en: jwtClaimsSet.getClaims().entrySet()) { 090 params.put(en.getKey(), Collections.singletonList(en.getValue() + "")); 091 } 092 093 return params; 094 } 095 096 097 /** 098 * Returns {@code true} if the specified JWT-secured authorisation 099 * response implies an error response. Note that the JWT is not 100 * validated in any way! 101 * 102 * @param jwtString The JWT-secured authorisation response string. Must 103 * not be {@code null}. 104 * 105 * @return {@code true} if an error is implied by the presence of the 106 * {@code error} claim, else {@code false} (also for encrypted 107 * JWTs which payload cannot be inspected without decrypting 108 * first). 109 * 110 * @throws ParseException If the JWT is invalid or plain (unsecured). 111 */ 112 public static boolean impliesAuthorizationErrorResponse(final String jwtString) 113 throws ParseException { 114 115 try { 116 return impliesAuthorizationErrorResponse(JWTParser.parse(jwtString)); 117 } catch (java.text.ParseException e) { 118 throw new ParseException("Invalid JWT-secured authorization response: " + e.getMessage(), e); 119 } 120 } 121 122 123 /** 124 * Returns {@code true} if the specified JWT-secured authorisation 125 * response implies an error response. Note that the JWT is not 126 * validated in any way! 127 * 128 * @param jwt The JWT-secured authorisation response. Must not be 129 * {@code null}. 130 * 131 * @return {@code true} if an error is implied by the presence of the 132 * {@code error} claim, else {@code false} (also for encrypted 133 * JWTs which payload cannot be inspected without decrypting 134 * first). 135 * 136 * @throws ParseException If the JWT is plain (unsecured). 137 */ 138 public static boolean impliesAuthorizationErrorResponse(final JWT jwt) 139 throws ParseException { 140 141 if (jwt instanceof PlainJWT) { 142 throw new ParseException("Invalid JWT-secured authorization response: The JWT must not be plain (unsecured)"); 143 } 144 145 if (jwt instanceof EncryptedJWT) { 146 // Cannot peek into payload 147 return false; 148 } 149 150 if (jwt instanceof SignedJWT) { 151 152 SignedJWT signedJWT = (SignedJWT)jwt; 153 154 try { 155 return signedJWT.getJWTClaimsSet().getStringClaim("error") != null; 156 } catch (java.text.ParseException e) { 157 throw new ParseException("Invalid JWT claims set: " + e.getMessage()); 158 } 159 } 160 161 throw new ParseException("Unexpected JWT type"); 162 } 163 164 165 /** 166 * Prevents public instantiation. 167 */ 168 private JARMUtils() {} 169}