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