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 reserved ones.
023 *
024 * @author Vladimir Dzhuvinov
025 * @version $version$ (2013-08-20)
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 specified.
112         */
113        public void setContentType(final String cty) {
114
115                this.cty = cty;
116        }
117
118
119        @Override
120        public Set<String> getCriticalHeaders() {
121
122                return crit;
123        }
124
125
126
127        /**
128         * Sets the critical headers ({@code crit}) parameter.
129         *
130         * @param crit The names of the critical header parameters, empty set 
131         *             {@code null} if none.
132         */
133        public void setCriticalHeaders(Set<String> crit) {
134
135                this.crit = crit;
136        }
137
138
139        @Override
140        public Object getCustomParameter(final String name) {
141
142                return customParameters.get(name);
143        }
144
145
146        /**
147         * Sets a custom (non-reserved) parameter. Callers and extending classes
148         * should ensure the parameter name doesn't match a reserved parameter 
149         * name.
150         *
151         * @param name  The name of the custom parameter. Must not match a 
152         *              reserved parameter name and must not be {@code null}.
153         * @param value The value of the custom parameter, should map to a valid
154         *              JSON entity, {@code null} if not specified.
155         */
156        protected void setCustomParameter(final String name, final Object value) {
157
158                customParameters.put(name, value);
159        }
160
161
162        @Override
163        public Map<String,Object> getCustomParameters() {
164
165                return Collections.unmodifiableMap(customParameters);
166        }
167
168
169        /**
170         * Sets the custom (non-reserved) parameters. The values must be 
171         * serialisable to a JSON entity, otherwise will be ignored.
172         *
173         * @param customParameters The custom parameters, empty map or 
174         *                         {@code null} if none.
175         */
176        public void setCustomParameters(final Map<String,Object> customParameters) {
177
178                if (customParameters == null) {
179                        return;
180                }
181
182                this.customParameters = customParameters;
183        }
184
185
186        /**
187         * Sets the original parsed Base64URL used to create this header.
188         *
189         * @param parsedBase64URL The parsed Base64URL, {@code null} if the 
190         *                        header was created from scratch.
191         */
192        protected void setParsedBase64URL(final Base64URL parsedBase64URL) {
193
194                this.parsedBase64URL = parsedBase64URL;
195        }
196
197
198        @Override
199        public JSONObject toJSONObject() {
200
201                // Include custom parameters, they will be overwritten if their
202                // names match specified reserved ones
203                JSONObject o = new JSONObject(customParameters);
204
205                // Alg is always defined
206                o.put("alg", alg.toString());
207
208                if (typ != null) {
209                        o.put("typ", typ.toString());
210                }
211
212                if (cty != null) {
213                        o.put("cty", cty);
214                }
215
216                if (crit != null && ! crit.isEmpty()) {
217                        o.put("crit", new ArrayList<String>(crit));
218                }
219
220                return o;
221        }
222
223
224        @Override
225        public String toString() {
226
227                return toJSONObject().toString();
228        }
229
230
231        @Override
232        public Base64URL toBase64URL() {
233
234                if (parsedBase64URL == null) {
235
236                        // Header was created from scratch, return new Base64URL
237                        return Base64URL.encode(toString());
238
239                } else {
240
241                        // Header was parsed, return original Base64URL
242                        return parsedBase64URL;
243                }
244        }
245
246
247        /**
248         * Parses an algorithm ({@code alg}) parameter from the specified 
249         * header JSON object. Intended for initial parsing of plain, JWS and 
250         * JWE headers.
251         *
252         * <p>The algorithm type (none, JWS or JWE) is determined by inspecting
253         * the algorithm name for "none" and the presence of an "enc" parameter.
254         *
255         * @param json The JSON object to parse. Must not be {@code null}.
256         *
257         * @return The algorithm, an instance of {@link Algorithm#NONE},
258         *         {@link JWSAlgorithm} or {@link JWEAlgorithm}.
259         *
260         * @throws ParseException If the {@code alg} parameter couldn't be 
261         *                        parsed.
262         */
263        public static Algorithm parseAlgorithm(final JSONObject json)
264                        throws ParseException {
265
266                String algName = JSONObjectUtils.getString(json, "alg");
267
268                // Infer algorithm type
269
270                if (algName.equals(Algorithm.NONE.getName())) {
271                        // Plain
272                        return Algorithm.NONE;
273                } else if (json.containsKey("enc")) {
274                        // JWE
275                        return JWEAlgorithm.parse(algName);
276                } else {
277                        // JWS
278                        return JWSAlgorithm.parse(algName);
279                }
280        }
281
282
283        /**
284         * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 
285         * from the specified JSON object.
286         *
287         * @param json The JSON object to parse. Must not be {@code null}.
288         *
289         * @return The header.
290         *
291         * @throws ParseException If the specified JSON object doesn't 
292         *                        represent a valid header.
293         */
294        public static Header parse(final JSONObject json)
295                throws ParseException {
296
297                Algorithm alg = parseAlgorithm(json);
298
299                if (alg.equals(Algorithm.NONE)) {
300
301                        return PlainHeader.parse(json);
302
303                } else if (alg instanceof JWSAlgorithm) {
304
305                        return JWSHeader.parse(json);
306
307                } else if (alg instanceof JWEAlgorithm) {
308
309                        return JWEHeader.parse(json);
310
311                } else {
312
313                        throw new AssertionError("Unexpected algorithm type: " + alg);
314                }
315        }
316
317
318        /**
319         * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader}
320         * from the specified JSON object string.
321         *
322         * @param s The JSON object string to parse. Must not be {@code null}.
323         *
324         * @return The header.
325         *
326         * @throws ParseException If the specified JSON object string doesn't
327         *                        represent a valid header.
328         */
329        public static Header parse(final String s)
330                throws ParseException {
331
332                JSONObject jsonObject = JSONObjectUtils.parseJSONObject(s);
333
334                return parse(jsonObject);
335        }
336
337
338        /**
339         * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader}
340         * from the specified Base64URL.
341         *
342         * @param base64URL The Base64URL to parse. Must not be {@code null}.
343         *
344         * @return The header.
345         *
346         * @throws ParseException If the specified Base64URL doesn't represent a
347         *                        valid header.
348         */
349        public static Header parse(final Base64URL base64URL)
350                throws ParseException {
351
352                Header header = parse(base64URL.decodeToString());
353                header.setParsedBase64URL(base64URL);
354                return header;
355        }
356}