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        /**
061         * Cached {@code unsupported_grant_type} exception.
062         */
063        private static final ParseException UNSUPPORTED_GRANT_TYPE_EXCEPTION
064                = new ParseException("The \"grant_type\" must be " + GRANT_TYPE, OAuth2Error.UNSUPPORTED_GRANT_TYPE);
065
066
067        /**
068         * Cached plain JOSE / JWT rejected exception.
069         */
070        private static final ParseException PLAIN_ASSERTION_REJECTED_EXCEPTION
071                = new ParseException("The JWT assertion must not be unsecured (plain)", OAuth2Error.INVALID_REQUEST);
072
073
074        /**
075         * Cached JWT assertion parse exception.
076         */
077        private static final ParseException JWT_PARSE_EXCEPTION
078                = new ParseException("The \"assertion\" is not a JWT", OAuth2Error.INVALID_REQUEST);
079
080        /**
081         * The assertion - signed JWT, encrypted JWT or nested signed+encrypted
082         * JWT.
083         */
084        private final JOSEObject assertion;
085
086
087        /**
088         * Creates a new signed JSON Web Token (JWT) bearer assertion grant.
089         *
090         * @param assertion The signed JSON Web Token (JWT) assertion. Must not
091         *                  be in a unsigned state or {@code null}. The JWT
092         *                  claims are not validated for compliance with the
093         *                  standard.
094         */
095        public JWTBearerGrant(final SignedJWT assertion) {
096
097                super(GRANT_TYPE);
098
099                if (assertion.getState().equals(JWSObject.State.UNSIGNED))
100                        throw new IllegalArgumentException("The JWT assertion must not be in a unsigned state");
101
102                this.assertion = assertion;
103        }
104
105
106        /**
107         * Creates a new nested signed and encrypted JSON Web Token (JWT)
108         * bearer assertion grant.
109         *
110         * @param assertion The nested signed and encrypted JSON Web Token
111         *                  (JWT) assertion. Must not be in a unencrypted state
112         *                  or {@code null}. The JWT claims are not validated
113         *                  for compliance with the standard.
114         */
115        public JWTBearerGrant(final JWEObject assertion) {
116
117                super(GRANT_TYPE);
118
119                if (assertion.getState().equals(JWEObject.State.UNENCRYPTED))
120                        throw new IllegalArgumentException("The JWT assertion must not be in a unencrypted state");
121
122                this.assertion = assertion;
123        }
124
125
126        /**
127         * Creates a new signed and encrypted JSON Web Token (JWT) bearer
128         * assertion grant.
129         *
130         * @param assertion The signed and encrypted JSON Web Token (JWT)
131         *                  assertion. Must not be in a unencrypted state or
132         *                  {@code null}. The JWT claims are not validated for
133         *                  compliance with the standard.
134         */
135        public JWTBearerGrant(final EncryptedJWT assertion) {
136
137                this((JWEObject) assertion);
138        }
139
140
141        /**
142         * Gets the JSON Web Token (JWT) bearer assertion.
143         *
144         * @return The assertion as a signed or encrypted JWT, {@code null} if
145         *         the assertion is a signed and encrypted JWT.
146         */
147        public JWT getJWTAssertion() {
148
149                return assertion instanceof JWT ?  (JWT)assertion : null;
150        }
151
152
153        /**
154         * Gets the JSON Web Token (JWT) bearer assertion.
155         *
156         * @return The assertion as a generic JOSE object (signed JWT,
157         *         encrypted JWT, or signed and encrypted JWT).
158         */
159        public JOSEObject getJOSEAssertion() {
160
161                return assertion;
162        }
163
164
165        @Override
166        public String getAssertion() {
167
168                return assertion.serialize();
169        }
170
171
172        @Override
173        public Map<String,String> toParameters() {
174
175                Map<String,String> params = new LinkedHashMap<>();
176                params.put("grant_type", GRANT_TYPE.getValue());
177                params.put("assertion", assertion.serialize());
178                return params;
179        }
180
181
182        /**
183         * Parses a JWT bearer grant from the specified parameters. The JWT
184         * claims are not validated for compliance with the standard.
185         *
186         * <p>Example:
187         *
188         * <pre>
189         * grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
190         * &assertion=eyJhbGciOiJFUzI1NiJ9.eyJpc3Mi[...omitted for brevity...].
191         * J9l-ZhwP[...omitted for brevity...]
192         * </pre>
193         *
194         * @param params The parameters.
195         *
196         * @return The JWT bearer grant.
197         *
198         * @throws ParseException If parsing failed.
199         */
200        public static JWTBearerGrant parse(final Map<String,String> params)
201                throws ParseException {
202
203                // Parse grant type
204                String grantTypeString = params.get("grant_type");
205
206                if (grantTypeString == null)
207                        throw MISSING_GRANT_TYPE_PARAM_EXCEPTION;
208
209                if (! GrantType.parse(grantTypeString).equals(GRANT_TYPE))
210                        throw UNSUPPORTED_GRANT_TYPE_EXCEPTION;
211
212                // Parse JWT assertion
213                String assertionString = params.get("assertion");
214
215                if (assertionString == null || assertionString.trim().isEmpty())
216                        throw MISSING_ASSERTION_PARAM_EXCEPTION;
217
218                try {
219                        final JOSEObject assertion = JOSEObject.parse(assertionString);
220
221                        if (assertion instanceof PlainObject) {
222
223                                throw PLAIN_ASSERTION_REJECTED_EXCEPTION;
224
225                        } else if (assertion instanceof JWSObject) {
226
227                                return new JWTBearerGrant(new SignedJWT(
228                                                assertion.getParsedParts()[0],
229                                                assertion.getParsedParts()[1],
230                                                assertion.getParsedParts()[2]));
231
232                        } else {
233                                // JWE
234
235                                if ("JWT".equalsIgnoreCase(assertion.getHeader().getContentType())) {
236                                        // Assume nested: signed JWT inside JWE
237                                        // http://tools.ietf.org/html/rfc7519#section-5.2
238                                        return new JWTBearerGrant((JWEObject)assertion);
239                                } else {
240                                        // Assume encrypted JWT
241                                        return new JWTBearerGrant(new EncryptedJWT(
242                                                        assertion.getParsedParts()[0],
243                                                        assertion.getParsedParts()[1],
244                                                        assertion.getParsedParts()[2],
245                                                        assertion.getParsedParts()[3],
246                                                        assertion.getParsedParts()[4]));
247                                }
248                        }
249
250                } catch (java.text.ParseException e) {
251                        throw JWT_PARSE_EXCEPTION;
252                }
253        }
254}