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