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