001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd.
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.jose;
019
020
021import java.text.ParseException;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.Map;
026import java.util.Set;
027
028import net.jcip.annotations.Immutable;
029
030import net.minidev.json.JSONObject;
031
032import com.nimbusds.jose.util.Base64URL;
033import com.nimbusds.jose.util.JSONObjectUtils;
034
035
036/**
037 * Unsecured ({@code alg=none}) JOSE header. This class is immutable.
038 *
039 * <p>Supports all {@link #getRegisteredParameterNames registered header
040 * parameters} of the unsecured JOSE object specification:
041 *
042 * <ul>
043 *     <li>alg (set to {@link Algorithm#NONE "none"}).
044 *     <li>typ
045 *     <li>cty
046 *     <li>crit
047 * </ul>
048 *
049 * <p>The header may also carry {@link #getCustomParams custom parameters};
050 * these will be serialised and parsed along the registered ones.
051 *
052 * <p>Example:
053 *
054 * <pre>
055 * {
056 *   "alg" : "none"
057 * }
058 * </pre>
059 *
060 * @author Vladimir Dzhuvinov
061 * @version 2014-08-20
062 */
063@Immutable
064public final class PlainHeader extends Header {
065
066
067        private static final long serialVersionUID = 1L;
068
069
070        /**
071         * The registered parameter names.
072         */
073        private static final Set<String> REGISTERED_PARAMETER_NAMES;
074
075
076        /**
077         * Initialises the registered parameter name set.
078         */
079        static {
080                Set<String> p = new HashSet<>();
081
082                p.add("alg");
083                p.add("typ");
084                p.add("cty");
085                p.add("crit");
086
087                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
088        }
089
090
091        /**
092         * Builder for constructing unsecured (plain) headers.
093         *
094         * <p>Example usage:
095         *
096         * <pre>
097         * PlainHeader header = new PlainHeader.Builder().
098         *                      contentType("text/plain").
099         *                      customParam("exp", new Date().getTime()).
100         *                      build();
101         * </pre>
102         */
103        public static class Builder {
104
105
106                /**
107                 * The JOSE object type.
108                 */
109                private JOSEObjectType typ;
110
111
112                /**
113                 * The content type.
114                 */
115                private String cty;
116
117
118                /**
119                 * The critical headers.
120                 */
121                private Set<String> crit;
122
123
124                /**
125                 * Custom header parameters.
126                 */
127                private Map<String,Object> customParams;
128
129
130                /**
131                 * The parsed Base64URL.
132                 */
133                private Base64URL parsedBase64URL;
134
135
136                /**
137                 * Creates a new unsecured (plain) header builder.
138                 */
139                public Builder() {
140
141                }
142
143
144                /**
145                 * Creates a new unsecured (plain) header builder with the
146                 * parameters from the specified header.
147                 *
148                 * @param plainHeader The unsecured header to use. Must not be
149                 *                    {@code null}.
150                 */
151                public Builder(final PlainHeader plainHeader) {
152
153                        typ = plainHeader.getType();
154                        cty = plainHeader.getContentType();
155                        crit = plainHeader.getCriticalParams();
156                        customParams = plainHeader.getCustomParams();
157                }
158
159
160                /**
161                 * Sets the type ({@code typ}) parameter.
162                 *
163                 * @param typ The type parameter, {@code null} if not
164                 *            specified.
165                 *
166                 * @return This builder.
167                 */
168                public Builder type(final JOSEObjectType typ) {
169
170                        this.typ = typ;
171                        return this;
172                }
173
174
175                /**
176                 * Sets the content type ({@code cty}) parameter.
177                 *
178                 * @param cty The content type parameter, {@code null} if not
179                 *            specified.
180                 *
181                 * @return This builder.
182                 */
183                public Builder contentType(final String cty) {
184
185                        this.cty = cty;
186                        return this;
187                }
188
189
190                /**
191                 * Sets the critical header parameters ({@code crit})
192                 * parameter.
193                 *
194                 * @param crit The names of the critical header parameters,
195                 *             empty set or {@code null} if none.
196                 *
197                 * @return This builder.
198                 */
199                public Builder criticalParams(final Set<String> crit) {
200
201                        this.crit = crit;
202                        return this;
203                }
204
205
206                /**
207                 * Sets a custom (non-registered) parameter.
208                 *
209                 * @param name  The name of the custom parameter. Must not
210                 *              match a registered parameter name and must not
211                 *              be {@code null}.
212                 * @param value The value of the custom parameter, should map
213                 *              to a valid JSON entity, {@code null} if not
214                 *              specified.
215                 *
216                 * @return This builder.
217                 *
218                 * @throws IllegalArgumentException If the specified parameter
219                 *                                  name matches a registered
220                 *                                  parameter name.
221                 */
222                public Builder customParam(final String name, final Object value) {
223
224                        if (getRegisteredParameterNames().contains(name)) {
225                                throw new IllegalArgumentException("The parameter name \"" + name + "\" matches a registered name");
226                        }
227
228                        if (customParams == null) {
229                                customParams = new HashMap<>();
230                        }
231
232                        customParams.put(name, value);
233
234                        return this;
235                }
236
237
238                /**
239                 * Sets the custom (non-registered) parameters. The values must
240                 * be serialisable to a JSON entity, otherwise will be ignored.
241                 *
242                 * @param customParameters The custom parameters, empty map or
243                 *                         {@code null} if none.
244                 *
245                 * @return This builder.
246                 */
247                public Builder customParams(final Map<String, Object> customParameters) {
248
249                        this.customParams = customParameters;
250                        return this;
251                }
252
253
254                /**
255                 * Sets the parsed Base64URL.
256                 *
257                 * @param base64URL The parsed Base64URL, {@code null} if the
258                 *                  header is created from scratch.
259                 *
260                 * @return This builder.
261                 */
262                public Builder parsedBase64URL(final Base64URL base64URL) {
263
264                        this.parsedBase64URL = base64URL;
265                        return this;
266                }
267
268
269                /**
270                 * Builds a new unsecured (plain) header.
271                 *
272                 * @return The unsecured header.
273                 */
274                public PlainHeader build() {
275
276                        return new PlainHeader(typ, cty, crit, customParams, parsedBase64URL);
277                }
278        }
279
280
281        /**
282         * Creates a new minimal unsecured (plain) header with algorithm
283         * {@link Algorithm#NONE none}.
284         */
285        public PlainHeader() {
286
287                this(null, null, null, null, null);
288        }
289
290
291        /**
292         * Creates a new unsecured (plain) header with algorithm
293         * {@link Algorithm#NONE none}.
294         *
295         * @param typ             The type ({@code typ}) parameter,
296         *                        {@code null} if not specified.
297         * @param cty             The content type ({@code cty}) parameter,
298         *                        {@code null} if not specified.
299         * @param crit            The names of the critical header
300         *                        ({@code crit}) parameters, empty set or
301         *                        {@code null} if none.
302         * @param customParams    The custom parameters, empty map or
303         *                        {@code null} if none.
304         * @param parsedBase64URL The parsed Base64URL, {@code null} if the
305         *                        header is created from scratch.
306         */
307        public PlainHeader(final JOSEObjectType typ,
308                           final String cty,
309                           final Set<String> crit,
310                           final Map<String, Object> customParams,
311                           final Base64URL parsedBase64URL) {
312
313                super(Algorithm.NONE, typ, cty, crit, customParams, parsedBase64URL);
314        }
315
316
317        /**
318         * Deep copy constructor.
319         *
320         * @param plainHeader The unsecured header to copy. Must not be
321         *                    {@code null}.
322         */
323        public PlainHeader(final PlainHeader plainHeader) {
324
325                this(
326                        plainHeader.getType(),
327                        plainHeader.getContentType(),
328                        plainHeader.getCriticalParams(),
329                        plainHeader.getCustomParams(),
330                        plainHeader.getParsedBase64URL()
331                );
332        }
333
334
335        /**
336         * Gets the registered parameter names for unsecured headers.
337         *
338         * @return The registered parameter names, as an unmodifiable set.
339         */
340        public static Set<String> getRegisteredParameterNames() {
341
342                return REGISTERED_PARAMETER_NAMES;
343        }
344
345
346        /**
347         * Gets the algorithm ({@code alg}) parameter.
348         *
349         * @return {@link Algorithm#NONE}.
350         */
351        @Override
352        public Algorithm getAlgorithm() {
353
354                return Algorithm.NONE;
355        }
356
357
358        /**
359         * Parses an unsecured header from the specified JSON object.
360         *
361         * @param jsonObject The JSON object to parse. Must not be {@code null}.
362         *
363         * @return The unsecured header.
364         *
365         * @throws ParseException If the specified JSON object doesn't
366         *                        represent a valid unsecured header.
367         */
368        public static PlainHeader parse(final JSONObject jsonObject)
369                throws ParseException {
370
371                return parse(jsonObject, null);
372        }
373
374
375        /**
376         * Parses an unsecured header from the specified JSON object.
377         *
378         * @param jsonObject      The JSON object to parse. Must not be
379         *                        {@code null}.
380         * @param parsedBase64URL The original parsed Base64URL, {@code null}
381         *                        if not applicable.
382         *
383         * @return The unsecured header.
384         *
385         * @throws ParseException If the specified JSON object doesn't
386         *                        represent a valid unsecured header.
387         */
388        public static PlainHeader parse(final JSONObject jsonObject,
389                                        final Base64URL parsedBase64URL)
390                throws ParseException {
391
392                // Get the "alg" parameter
393                Algorithm alg = Header.parseAlgorithm(jsonObject);
394
395                if (alg != Algorithm.NONE) {
396                        throw new ParseException("The algorithm \"alg\" header parameter must be \"none\"", 0);
397                }
398
399                PlainHeader.Builder header = new Builder().parsedBase64URL(parsedBase64URL);
400
401                // Parse optional + custom parameters
402                for(final String name: jsonObject.keySet()) {
403
404
405                        
406                        if("alg".equals(name)) {
407                                // skip
408                        } else if("typ".equals(name)) {
409                                header = header.type(new JOSEObjectType(JSONObjectUtils.getString(jsonObject, name)));
410                        } else if("cty".equals(name)) {
411                                header = header.contentType(JSONObjectUtils.getString(jsonObject, name));
412                        } else if("crit".equals(name)) {
413                                header = header.criticalParams(new HashSet<>(JSONObjectUtils.getStringList(jsonObject, name)));
414                        } else {
415                                header = header.customParam(name, jsonObject.get(name));
416                        }
417                }
418
419                return header.build();
420        }
421
422
423        /**
424         * Parses an unsecured header from the specified JSON string.
425         *
426         * @param jsonString The JSON string to parse. Must not be
427         *                   {@code null}.
428         *
429         * @return The unsecured header.
430         *
431         * @throws ParseException If the specified JSON string doesn't
432         *                        represent a valid unsecured header.
433         */
434        public static PlainHeader parse(final String jsonString)
435                throws ParseException {
436
437                return parse(jsonString, null);
438        }
439
440
441        /**
442         * Parses an unsecured header from the specified JSON string.
443         *
444         * @param jsonString      The JSON string to parse. Must not be
445         *                        {@code null}.
446         * @param parsedBase64URL The original parsed Base64URL, {@code null}
447         *                        if not applicable.
448         *
449         * @return The unsecured header.
450         *
451         * @throws ParseException If the specified JSON string doesn't 
452         *                        represent a valid unsecured header.
453         */
454        public static PlainHeader parse(final String jsonString,
455                                        final Base64URL parsedBase64URL)
456                throws ParseException {
457
458                return parse(JSONObjectUtils.parse(jsonString), parsedBase64URL);
459        }
460
461
462        /**
463         * Parses an unsecured header from the specified Base64URL.
464         *
465         * @param base64URL The Base64URL to parse. Must not be {@code null}.
466         *
467         * @return The unsecured header.
468         *
469         * @throws ParseException If the specified Base64URL doesn't represent
470         *                        a valid unsecured header.
471         */
472        public static PlainHeader parse(final Base64URL base64URL)
473                throws ParseException {
474
475                return parse(base64URL.decodeToString(), base64URL);
476        }
477}