001package com.nimbusds.jose;
002
003
004import java.text.ParseException;
005import java.util.ArrayList;
006import java.util.Collections;
007import java.util.HashMap;
008import java.util.Map;
009import java.util.Set;
010
011import net.minidev.json.JSONObject;
012
013import com.nimbusds.jose.util.Base64URL;
014import com.nimbusds.jose.util.JSONObjectUtils;
015
016
017/**
018 * The base abstract class for plaintext, JSON Web Signature (JWS) and JSON Web 
019 * Encryption (JWE) headers.
020 *
021 * <p>The header may also carry {@link #setCustomParameters custom parameters};
022 * these will be serialised and parsed along the registered ones.
023 *
024 * @author Vladimir Dzhuvinov
025 * @version $version$ (2013-10-07)
026 */
027public abstract class Header implements ReadOnlyHeader {
028
029
030        /**
031         * The algorithm ({@code alg}) parameter.
032         */
033        final protected Algorithm alg;
034
035
036        /**
037         * The JOSE object type ({@code typ}) parameter.
038         */
039        private JOSEObjectType typ;
040
041
042        /**
043         * The content type ({@code cty}) parameter.
044         */
045        private String cty;
046
047
048        /**
049         * The critical headers ({@code crit}) parameter.
050         */
051        private Set<String> crit;
052
053
054        /**
055         * Custom header parameters.
056         */
057        private Map<String,Object> customParameters = new HashMap<String,Object>();
058
059
060        /**
061         * The original parsed Base64URL, {@code null} if the header was 
062         * created from scratch.
063         */
064        private Base64URL parsedBase64URL;
065
066
067        /**
068         * Creates a new header with the specified algorithm ({@code alg}) 
069         * parameter.
070         *
071         * @param alg The algorithm parameter. Must not be {@code null}.
072         */
073        protected Header(final Algorithm alg) {
074
075                if (alg == null) {
076                        throw new IllegalArgumentException("The algorithm \"alg\" header parameter must not be null");
077                }
078
079                this.alg = alg;
080        }
081
082
083        @Override
084        public JOSEObjectType getType() {
085
086                return typ;
087        }
088
089
090        /**
091         * Sets the type ({@code typ}) parameter.
092         *
093         * @param typ The type parameter, {@code null} if not specified.
094         */
095        public void setType(final JOSEObjectType typ) {
096
097                this.typ = typ;
098        }
099
100
101        @Override
102        public String getContentType() {
103
104                return cty;
105        }
106
107
108        /**
109         * Sets the content type ({@code cty}) parameter.
110         *
111         * @param cty The content type parameter, {@code null} if not
112         *            specified.
113         */
114        public void setContentType(final String cty) {
115
116                this.cty = cty;
117        }
118
119
120        @Override
121        public Set<String> getCriticalHeaders() {
122
123                return crit;
124        }
125
126
127
128        /**
129         * Sets the critical headers ({@code crit}) parameter.
130         *
131         * @param crit The names of the critical header parameters, empty set 
132         *             {@code null} if none.
133         */
134        public void setCriticalHeaders(Set<String> crit) {
135
136                this.crit = crit;
137        }
138
139
140        @Override
141        public Object getCustomParameter(final String name) {
142
143                return customParameters.get(name);
144        }
145
146
147        /**
148         * Sets a custom (non-registered) parameter. Callers and extending
149         * classes should ensure the parameter name doesn't match a registered
150         * parameter name.
151         *
152         * @param name  The name of the custom parameter. Must not match a 
153         *              registered parameter name and must not be {@code null}.
154         * @param value The value of the custom parameter, should map to a
155         *              valid JSON entity, {@code null} if not specified.
156         */
157        protected void setCustomParameter(final String name, final Object value) {
158
159                customParameters.put(name, value);
160        }
161
162
163        @Override
164        public Map<String,Object> getCustomParameters() {
165
166                return Collections.unmodifiableMap(customParameters);
167        }
168
169
170        /**
171         * Sets the custom (non-registered) parameters. The values must be
172         * serialisable to a JSON entity, otherwise will be ignored.
173         *
174         * @param customParameters The custom parameters, empty map or 
175         *                         {@code null} if none.
176         */
177        public void setCustomParameters(final Map<String,Object> customParameters) {
178
179                if (customParameters == null) {
180                        return;
181                }
182
183                this.customParameters = customParameters;
184        }
185
186
187        /**
188         * Sets the original parsed Base64URL used to create this header.
189         *
190         * @param parsedBase64URL The parsed Base64URL, {@code null} if the 
191         *                        header was created from scratch.
192         */
193        protected void setParsedBase64URL(final Base64URL parsedBase64URL) {
194
195                this.parsedBase64URL = parsedBase64URL;
196        }
197
198
199        @Override
200        public JSONObject toJSONObject() {
201
202                // Include custom parameters, they will be overwritten if their
203                // names match specified registered ones
204                JSONObject o = new JSONObject(customParameters);
205
206                // Alg is always defined
207                o.put("alg", alg.toString());
208
209                if (typ != null) {
210                        o.put("typ", typ.toString());
211                }
212
213                if (cty != null) {
214                        o.put("cty", cty);
215                }
216
217                if (crit != null && ! crit.isEmpty()) {
218                        o.put("crit", new ArrayList<String>(crit));
219                }
220
221                return o;
222        }
223
224
225        @Override
226        public String toString() {
227
228                return toJSONObject().toString();
229        }
230
231
232        @Override
233        public Base64URL toBase64URL() {
234
235                if (parsedBase64URL == null) {
236
237                        // Header was created from scratch, return new Base64URL
238                        return Base64URL.encode(toString());
239
240                } else {
241
242                        // Header was parsed, return original Base64URL
243                        return parsedBase64URL;
244                }
245        }
246
247
248        /**
249         * Parses an algorithm ({@code alg}) parameter from the specified 
250         * header JSON object. Intended for initial parsing of plain, JWS and 
251         * JWE headers.
252         *
253         * <p>The algorithm type (none, JWS or JWE) is determined by inspecting
254         * the algorithm name for "none" and the presence of an "enc"
255         * parameter.
256         *
257         * @param json The JSON object to parse. Must not be {@code null}.
258         *
259         * @return The algorithm, an instance of {@link Algorithm#NONE},
260         *         {@link JWSAlgorithm} or {@link JWEAlgorithm}.
261         *
262         * @throws ParseException If the {@code alg} parameter couldn't be 
263         *                        parsed.
264         */
265        public static Algorithm parseAlgorithm(final JSONObject json)
266                        throws ParseException {
267
268                String algName = JSONObjectUtils.getString(json, "alg");
269
270                // Infer algorithm type
271
272                if (algName.equals(Algorithm.NONE.getName())) {
273                        // Plain
274                        return Algorithm.NONE;
275                } else if (json.containsKey("enc")) {
276                        // JWE
277                        return JWEAlgorithm.parse(algName);
278                } else {
279                        // JWS
280                        return JWSAlgorithm.parse(algName);
281                }
282        }
283
284
285        /**
286         * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 
287         * from the specified JSON object.
288         *
289         * @param json The JSON object to parse. Must not be {@code null}.
290         *
291         * @return The header.
292         *
293         * @throws ParseException If the specified JSON object doesn't 
294         *                        represent a valid header.
295         */
296        public static Header parse(final JSONObject json)
297                throws ParseException {
298
299                Algorithm alg = parseAlgorithm(json);
300
301                if (alg.equals(Algorithm.NONE)) {
302
303                        return PlainHeader.parse(json);
304
305                } else if (alg instanceof JWSAlgorithm) {
306
307                        return JWSHeader.parse(json);
308
309                } else if (alg instanceof JWEAlgorithm) {
310
311                        return JWEHeader.parse(json);
312
313                } else {
314
315                        throw new AssertionError("Unexpected algorithm type: " + alg);
316                }
317        }
318
319
320        /**
321         * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader}
322         * from the specified JSON object string.
323         *
324         * @param s The JSON object string to parse. Must not be {@code null}.
325         *
326         * @return The header.
327         *
328         * @throws ParseException If the specified JSON object string doesn't
329         *                        represent a valid header.
330         */
331        public static Header parse(final String s)
332                throws ParseException {
333
334                JSONObject jsonObject = JSONObjectUtils.parseJSONObject(s);
335
336                return parse(jsonObject);
337        }
338
339
340        /**
341         * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader}
342         * from the specified Base64URL.
343         *
344         * @param base64URL The Base64URL to parse. Must not be {@code null}.
345         *
346         * @return The header.
347         *
348         * @throws ParseException If the specified Base64URL doesn't represent
349         *                        a valid header.
350         */
351        public static Header parse(final Base64URL base64URL)
352                throws ParseException {
353
354                Header header = parse(base64URL.decodeToString());
355                header.setParsedBase64URL(base64URL);
356                return header;
357        }
358}