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.assertions.jwt;
019
020
021import java.util.*;
022
023import net.jcip.annotations.Immutable;
024import net.minidev.json.JSONObject;
025
026import com.nimbusds.jwt.JWTClaimsSet;
027import com.nimbusds.jwt.util.DateUtils;
028import com.nimbusds.oauth2.sdk.ParseException;
029import com.nimbusds.oauth2.sdk.assertions.AssertionDetails;
030import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT;
031import com.nimbusds.oauth2.sdk.auth.PrivateKeyJWT;
032import com.nimbusds.oauth2.sdk.id.*;
033import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
034
035
036/**
037 * JSON Web Token (JWT) bearer assertion details (claims set) for OAuth 2.0
038 * client authentication and authorisation grants.
039 *
040 * <p>Used for {@link ClientSecretJWT client secret JWT} and
041 * {@link PrivateKeyJWT private key JWT} authentication at the Token endpoint
042 * as well as {@link com.nimbusds.oauth2.sdk.JWTBearerGrant JWT bearer
043 * assertion grants}.
044 *
045 * <p>Example JWT bearer assertion claims set for client authentication:
046 *
047 * <pre>
048 * {
049 *   "iss" : "http://client.example.com",
050 *   "sub" : "http://client.example.com",
051 *   "aud" : [ "http://idp.example.com/token" ],
052 *   "jti" : "d396036d-c4d9-40d8-8e98-f7e8327002d9",
053 *   "exp" : 1311281970,
054 *   "iat" : 1311280970
055 * }
056 * </pre>
057 *
058 * <p>Related specifications:
059 *
060 * <ul>
061 *     <li>JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and
062 *         Authorization Grants (RFC 7523), section 3.
063 * </ul>
064 */
065@Immutable
066public class JWTAssertionDetails extends AssertionDetails {
067
068
069        /**
070         * The names of the reserved JWT claims.
071         */
072        private static final Set<String> reservedClaimsNames = new LinkedHashSet<>();
073
074
075        static {
076                reservedClaimsNames.add("iss");
077                reservedClaimsNames.add("sub");
078                reservedClaimsNames.add("aud");
079                reservedClaimsNames.add("exp");
080                reservedClaimsNames.add("nbf");
081                reservedClaimsNames.add("iat");
082                reservedClaimsNames.add("jti");
083        }
084
085
086        /**
087         * Gets the names of the reserved JWT bearer assertion claims.
088         *
089         * @return The names of the reserved JWT bearer assertion claims
090         *         (read-only set).
091         */
092        public static Set<String> getReservedClaimsNames() {
093
094                return Collections.unmodifiableSet(reservedClaimsNames);
095        }
096
097
098        /**
099         * The time before which this token must not be accepted for
100         * processing (optional). The serialised value is number of seconds
101         * from 1970-01-01T0:0:0Z as measured in UTC until the desired
102         * date/time.
103         */
104        private final Date nbf;
105
106
107        /**
108         * Other optional custom claims.
109         */
110        private final Map<String,Object> other;
111
112
113        /**
114         * Creates a new JWT bearer assertion details (claims set) instance.
115         * The expiration time (exp) is set to five minutes from the current
116         * system time. Generates a default identifier (jti) for the JWT. The
117         * issued-at (iat) and not-before (nbf) claims are not set.
118         *
119         * @param iss The issuer identifier. Must not be {@code null}.
120         * @param sub The subject. Must not be {@code null}.
121         * @param aud The audience identifier, typically the URI of the
122         *            authorisation server's Token endpoint. Must not be
123         *            {@code null}.
124         */
125        public JWTAssertionDetails(final Issuer iss,
126                                   final Subject sub,
127                                   final Audience aud) {
128
129                this(iss, sub, aud.toSingleAudienceList(), new Date(new Date().getTime() + 5*60*1000L), null, null, new JWTID(), null);
130        }
131
132
133        /**
134         * Creates a new JWT bearer assertion details (claims set) instance.
135         *
136         * @param iss   The issuer identifier. Must not be {@code null}.
137         * @param sub   The subject. Must not be {@code null}.
138         * @param aud   The audience, typically including the URI of the
139         *              authorisation server's token endpoint. Must not be
140         *              {@code null}.
141         * @param exp   The expiration time. Must not be {@code null}.
142         * @param nbf   The time before which the token must not be accepted
143         *              for processing, {@code null} if not specified.
144         * @param iat   The time at which the token was issued, {@code null} if
145         *              not specified.
146         * @param jti   Unique identifier for the JWT, {@code null} if not
147         *              specified.
148         * @param other Other custom claims to include, {@code null} if none.
149         */
150        public JWTAssertionDetails(final Issuer iss,
151                                   final Subject sub,
152                                   final List<Audience> aud,
153                                   final Date exp,
154                                   final Date nbf,
155                                   final Date iat,
156                                   final JWTID jti,
157                                   final Map<String,Object> other) {
158
159                super(iss, sub, aud, iat, exp, jti);
160                this.nbf = nbf;
161                this.other = other;
162        }
163        
164        
165        /**
166         * Returns the optional not-before time. Corresponds to the {@code nbf}
167         * claim.
168         *
169         * @return The not-before time, {@code null} if not specified.
170         */
171        public Date getNotBeforeTime() {
172        
173                return nbf;
174        }
175
176
177        /**
178         * Returns the optional assertion identifier, as a JWT ID. Corresponds
179         * to the {@code jti} claim.
180         *
181         * @see #getID()
182         *
183         * @return The optional JWT ID, {@code null} if not specified.
184         */
185        public JWTID getJWTID() {
186
187                Identifier id = getID();
188                return id != null ? new JWTID(id.getValue()) : null;
189        }
190
191
192        /**
193         * Returns the custom claims.
194         *
195         * @return The custom claims, {@code null} if not specified.
196         */
197        public Map<String,Object> getCustomClaims() {
198
199                return other;
200        }
201        
202        
203        /**
204         * Returns a JSON object representation of this JWT bearer assertion
205         * details.
206         *
207         * @return The JSON object.
208         */
209        public JSONObject toJSONObject() {
210        
211                JSONObject o = new JSONObject();
212                
213                o.put("iss", getIssuer().getValue());
214                o.put("sub", getSubject().getValue());
215                o.put("aud", Audience.toStringList(getAudience()));
216                o.put("exp", DateUtils.toSecondsSinceEpoch(getExpirationTime()));
217
218                if (nbf != null)
219                        o.put("nbf", DateUtils.toSecondsSinceEpoch(nbf));
220                
221                if (getIssueTime() != null)
222                        o.put("iat", DateUtils.toSecondsSinceEpoch(getIssueTime()));
223                
224                if (getID() != null)
225                        o.put("jti", getID().getValue());
226
227                if (other != null) {
228                        o.putAll(other);
229                }
230
231                return o;
232        }
233
234
235        /**
236         * Returns a JSON Web Token (JWT) claims set representation of this
237         * JWT bearer assertion details.
238         *
239         * @return The JWT claims set.
240         */
241        public JWTClaimsSet toJWTClaimsSet() {
242
243                JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder()
244                        .issuer(getIssuer().getValue())
245                        .subject(getSubject().getValue())
246                        .audience(Audience.toStringList(getAudience()))
247                        .expirationTime(getExpirationTime())
248                        .notBeforeTime(nbf) // optional
249                        .issueTime(getIssueTime()) // optional
250                        .jwtID(getID() != null ? getJWTID().getValue() : null); // optional
251
252                // Append custom claims if any
253                if (other != null) {
254                        for (Map.Entry<String,?> entry: other.entrySet()) {
255                                builder = builder.claim(entry.getKey(), entry.getValue());
256                        }
257                }
258
259                return builder.build();
260        }
261        
262        
263        /**
264         * Parses a JWT bearer assertion details (claims set) instance from the
265         * specified JSON object.
266         *
267         * @param jsonObject The JSON object. Must not be {@code null}.
268         *
269         * @return The JWT bearer assertion details.
270         *
271         * @throws ParseException If the JSON object couldn't be parsed to a 
272         *                        JWT bearer assertion details instance.
273         */
274        public static JWTAssertionDetails parse(final JSONObject jsonObject)
275                throws ParseException {
276                
277                // Parse required claims
278                Issuer iss = new Issuer(JSONObjectUtils.getString(jsonObject, "iss"));
279                Subject sub = new Subject(JSONObjectUtils.getString(jsonObject, "sub"));
280
281                List<Audience> aud;
282
283                if (jsonObject.get("aud") instanceof String) {
284                        aud = new Audience(JSONObjectUtils.getString(jsonObject, "aud")).toSingleAudienceList();
285                } else {
286                        aud = Audience.create(JSONObjectUtils.getStringList(jsonObject, "aud"));
287                }
288
289                Date exp = DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(jsonObject, "exp"));
290
291
292                // Parse optional claims
293
294                Date nbf = null;
295
296                if (jsonObject.containsKey("nbf"))
297                        nbf = DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(jsonObject, "nbf"));
298
299                Date iat = null;
300
301                if (jsonObject.containsKey("iat"))
302                        iat = DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(jsonObject, "iat"));
303
304                JWTID jti = null;
305
306                if (jsonObject.containsKey("jti"))
307                        jti = new JWTID(JSONObjectUtils.getString(jsonObject, "jti"));
308
309                // Parse custom claims
310                Map<String,Object> other = null;
311
312                Set<String> customClaimNames = jsonObject.keySet();
313                if (customClaimNames.removeAll(reservedClaimsNames)) {
314                        other = new LinkedHashMap<>();
315                        for (String claim: customClaimNames) {
316                                other.put(claim, jsonObject.get(claim));
317                        }
318                }
319
320                return new JWTAssertionDetails(iss, sub, aud, exp, nbf, iat, jti, other);
321        }
322
323
324        /**
325         * Parses a JWT bearer assertion details instance from the specified
326         * JWT claims set.
327         *
328         * @param jwtClaimsSet The JWT claims set. Must not be {@code null}.
329         *
330         * @return The JWT bearer assertion details.
331         *
332         * @throws ParseException If the JWT claims set couldn't be parsed to a 
333         *                        JWT bearer assertion details instance.
334         */
335        public static JWTAssertionDetails parse(final JWTClaimsSet jwtClaimsSet)
336                throws ParseException {
337                
338                return parse(JSONObjectUtils.toJSONObject(jwtClaimsSet));
339        }
340}