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