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         * &amp;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}