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