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; 019 020 021import java.util.Collections; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Map; 025 026import net.jcip.annotations.Immutable; 027 028import com.nimbusds.jose.JOSEObject; 029import com.nimbusds.jose.JWEObject; 030import com.nimbusds.jose.JWSObject; 031import com.nimbusds.jose.PlainObject; 032import com.nimbusds.jwt.EncryptedJWT; 033import com.nimbusds.jwt.JWT; 034import com.nimbusds.jwt.SignedJWT; 035import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 036 037 038/** 039 * JWT bearer grant. Used in access token requests with a JSON Web Token (JWT), 040 * such an OpenID Connect ID token. 041 * 042 * <p>The JWT assertion can be: 043 * 044 * <ul> 045 * <li>Signed or MAC protected with JWS 046 * <li>Encrypted with JWE 047 * <li>Nested - signed / MAC protected with JWS and then encrypted with JWE 048 * </ul> 049 * 050 * <p>Related specifications: 051 * 052 * <ul> 053 * <li>Assertion Framework for OAuth 2.0 Client Authentication and 054 * Authorization Grants (RFC 7521), section 4.1. 055 * <li>JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and 056 * Authorization Grants (RFC 7523), section-2.1. 057 * </ul> 058 */ 059@Immutable 060public class JWTBearerGrant extends AssertionGrant { 061 062 063 /** 064 * The grant type. 065 */ 066 public static final GrantType GRANT_TYPE = GrantType.JWT_BEARER; 067 068 069 private static final String PLAIN_ASSERTION_REJECTED_MESSAGE = "The JWT assertion must not be unsecured (plain)"; 070 071 072 private static final String JWT_PARSE_MESSAGE = "The assertion is not a JWT"; 073 074 075 /** 076 * Cached plain JOSE / JWT rejected exception. 077 */ 078 private static final ParseException PLAIN_ASSERTION_REJECTED_EXCEPTION 079 = new ParseException(PLAIN_ASSERTION_REJECTED_MESSAGE, 080 OAuth2Error.INVALID_REQUEST.appendDescription(": " + PLAIN_ASSERTION_REJECTED_MESSAGE)); 081 082 083 /** 084 * Cached JWT assertion parse exception. 085 */ 086 private static final ParseException JWT_PARSE_EXCEPTION 087 = new ParseException(JWT_PARSE_MESSAGE, 088 OAuth2Error.INVALID_REQUEST.appendDescription(": " + JWT_PARSE_MESSAGE)); 089 090 /** 091 * The assertion - signed JWT, encrypted JWT or nested signed+encrypted 092 * JWT. 093 */ 094 private final JOSEObject assertion; 095 096 097 /** 098 * Creates a new signed JSON Web Token (JWT) bearer assertion grant. 099 * 100 * @param assertion The signed JSON Web Token (JWT) assertion. Must not 101 * be in a unsigned state or {@code null}. The JWT 102 * claims are not validated for compliance with the 103 * standard. 104 */ 105 public JWTBearerGrant(final SignedJWT assertion) { 106 107 super(GRANT_TYPE); 108 109 if (assertion.getState().equals(JWSObject.State.UNSIGNED)) 110 throw new IllegalArgumentException("The JWT assertion must not be in a unsigned state"); 111 112 this.assertion = assertion; 113 } 114 115 116 /** 117 * Creates a new nested signed and encrypted JSON Web Token (JWT) 118 * bearer assertion grant. 119 * 120 * @param assertion The nested signed and encrypted JSON Web Token 121 * (JWT) assertion. Must not be in a unencrypted state 122 * or {@code null}. The JWT claims are not validated 123 * for compliance with the standard. 124 */ 125 public JWTBearerGrant(final JWEObject assertion) { 126 127 super(GRANT_TYPE); 128 129 if (assertion.getState().equals(JWEObject.State.UNENCRYPTED)) 130 throw new IllegalArgumentException("The JWT assertion must not be in a unencrypted state"); 131 132 this.assertion = assertion; 133 } 134 135 136 /** 137 * Creates a new signed and encrypted JSON Web Token (JWT) bearer 138 * assertion grant. 139 * 140 * @param assertion The signed and encrypted JSON Web Token (JWT) 141 * assertion. Must not be in a unencrypted state or 142 * {@code null}. The JWT claims are not validated for 143 * compliance with the standard. 144 */ 145 public JWTBearerGrant(final EncryptedJWT assertion) { 146 147 this((JWEObject) assertion); 148 } 149 150 151 /** 152 * Gets the JSON Web Token (JWT) bearer assertion. 153 * 154 * @return The assertion as a signed or encrypted JWT, {@code null} if 155 * the assertion is a signed and encrypted JWT. 156 */ 157 public JWT getJWTAssertion() { 158 159 return assertion instanceof JWT ? (JWT)assertion : null; 160 } 161 162 163 /** 164 * Gets the JSON Web Token (JWT) bearer assertion. 165 * 166 * @return The assertion as a generic JOSE object (signed JWT, 167 * encrypted JWT, or signed and encrypted JWT). 168 */ 169 public JOSEObject getJOSEAssertion() { 170 171 return assertion; 172 } 173 174 175 @Override 176 public String getAssertion() { 177 178 return assertion.serialize(); 179 } 180 181 182 @Override 183 public Map<String,List<String>> toParameters() { 184 185 Map<String,List<String>> params = new LinkedHashMap<>(); 186 params.put("grant_type", Collections.singletonList(GRANT_TYPE.getValue())); 187 params.put("assertion", Collections.singletonList(assertion.serialize())); 188 return params; 189 } 190 191 192 /** 193 * Parses a JWT bearer grant from the specified request body 194 * parameters. The JWT claims are not validated for compliance with the 195 * standard. 196 * 197 * <p>Example: 198 * 199 * <pre> 200 * grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer 201 * &assertion=eyJhbGciOiJFUzI1NiJ9.eyJpc3Mi[...omitted for brevity...]. 202 * J9l-ZhwP[...omitted for brevity...] 203 * </pre> 204 * 205 * @param params The parameters. 206 * 207 * @return The JWT bearer grant. 208 * 209 * @throws ParseException If parsing failed. 210 */ 211 public static JWTBearerGrant parse(final Map<String,List<String>> params) 212 throws ParseException { 213 214 GrantType.ensure(GRANT_TYPE, params); 215 216 // Parse JWT assertion 217 String assertionString = MultivaluedMapUtils.getFirstValue(params, "assertion"); 218 219 if (assertionString == null || assertionString.trim().isEmpty()) 220 throw MISSING_ASSERTION_PARAM_EXCEPTION; 221 222 try { 223 final JOSEObject assertion = JOSEObject.parse(assertionString); 224 225 if (assertion instanceof PlainObject) { 226 227 throw PLAIN_ASSERTION_REJECTED_EXCEPTION; 228 229 } else if (assertion instanceof JWSObject) { 230 231 return new JWTBearerGrant(new SignedJWT( 232 assertion.getParsedParts()[0], 233 assertion.getParsedParts()[1], 234 assertion.getParsedParts()[2])); 235 236 } else { 237 // JWE 238 239 if ("JWT".equalsIgnoreCase(assertion.getHeader().getContentType())) { 240 // Assume nested: signed JWT inside JWE 241 // http://tools.ietf.org/html/rfc7519#section-5.2 242 return new JWTBearerGrant((JWEObject)assertion); 243 } else { 244 // Assume encrypted JWT 245 return new JWTBearerGrant(new EncryptedJWT( 246 assertion.getParsedParts()[0], 247 assertion.getParsedParts()[1], 248 assertion.getParsedParts()[2], 249 assertion.getParsedParts()[3], 250 assertion.getParsedParts()[4])); 251 } 252 } 253 254 } catch (java.text.ParseException e) { 255 throw JWT_PARSE_EXCEPTION; 256 } 257 } 258}