001package com.nimbusds.jose;
002
003
004import java.text.ParseException;
005import java.util.Collections;
006import java.util.HashSet;
007import java.util.Set;
008
009import net.minidev.json.JSONObject;
010
011import com.nimbusds.jose.jwk.JWK;
012import com.nimbusds.jose.util.Base64URL;
013import com.nimbusds.jose.util.JSONObjectUtils;
014import com.nimbusds.jose.util.X509CertChainUtils;
015
016
017/**
018 * JSON Web Signature (JWS) header.
019 *
020 * <p>Supports all {@link #getRegisteredParameterNames registered header
021 * parameters} of the JWS specification:
022 *
023 * <ul>
024 *     <li>alg
025 *     <li>jku
026 *     <li>jwk
027 *     <li>x5u
028 *     <li>x5t
029 *     <li>x5c
030 *     <li>kid
031 *     <li>typ
032 *     <li>cty
033 *     <li>crit
034 * </ul>
035 *
036 * <p>The header may also carry {@link #setCustomParameters custom parameters};
037 * these will be serialised and parsed along the registered ones.
038 *
039 * <p>Example header of a JSON Web Signature (JWS) object using the 
040 * {@link JWSAlgorithm#HS256 HMAC SHA-256 algorithm}:
041 *
042 * <pre>
043 * {
044 *   "alg" : "HS256"
045 * }
046 * </pre>
047 *
048 * @author Vladimir Dzhuvinov
049 * @version $version$ (2013-10-07)
050 */
051public class JWSHeader extends CommonSEHeader implements ReadOnlyJWSHeader {
052
053
054        /**
055         * The registered parameter names.
056         */
057        private static final Set<String> REGISTERED_PARAMETER_NAMES;
058
059
060        /**
061         * Initialises the registered parameter name set.
062         */
063        static {
064                Set<String> p = new HashSet<String>();
065
066                p.add("alg");
067                p.add("jku");
068                p.add("jwk");
069                p.add("x5u");
070                p.add("x5t");
071                p.add("x5c");
072                p.add("kid");
073                p.add("typ");
074                p.add("cty");
075                p.add("crit");
076
077                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
078        }
079
080
081        /**
082         * Creates a new JSON Web Signature (JWS) header.
083         *
084         * <p>Note: Use {@link PlainHeader} to create a header with algorithm
085         * {@link Algorithm#NONE none}.
086         *
087         * @param alg The JWS algorithm. Must not be "none" or {@code null}.
088         */
089        public JWSHeader(final JWSAlgorithm alg) {
090
091                super(alg);
092
093                if (alg.getName().equals(Algorithm.NONE.getName())) {
094                        throw new IllegalArgumentException("The JWS algorithm cannot be \"none\"");
095                }
096        }
097
098
099        /**
100         * Gets the registered parameter names for JWS headers.
101         *
102         * @return The registered parameter names, as an unmodifiable set.
103         */
104        public static Set<String> getRegisteredParameterNames() {
105
106                return REGISTERED_PARAMETER_NAMES;
107        }
108
109
110        @Override
111        public JWSAlgorithm getAlgorithm() {
112
113                return (JWSAlgorithm)alg;
114        }
115
116
117        /**
118         * @throws IllegalArgumentException If the specified parameter name
119         *                                  matches a registered parameter
120         *                                  name.
121         */
122        @Override
123        public void setCustomParameter(final String name, final Object value) {
124
125                if (getRegisteredParameterNames().contains(name)) {
126                        throw new IllegalArgumentException("The parameter name \"" + name + "\" matches a registered name");
127                }
128
129                super.setCustomParameter(name, value);
130        }
131
132
133        @Override
134        public Set<String> getIncludedParameters() {
135
136                Set<String> includedParameters = 
137                        new HashSet<String>(getCustomParameters().keySet());
138
139                includedParameters.add("alg");
140
141                if (getType() != null) {
142                        includedParameters.add("typ");
143                }
144
145                if (getContentType() != null) {
146                        includedParameters.add("cty");
147                }
148
149                if (getCriticalHeaders() != null && ! getCriticalHeaders().isEmpty()) {
150                        includedParameters.add("crit");
151                }
152
153                if (getJWKURL() != null) {
154                        includedParameters.add("jku");
155                }
156
157                if (getJWK() != null) {
158                        includedParameters.add("jwk");
159                }
160
161                if (getX509CertURL() != null) {
162                        includedParameters.add("x5u");
163                }
164
165                if (getX509CertThumbprint() != null) {
166                        includedParameters.add("x5t");
167                }
168
169                if (getX509CertChain() != null) {
170                        includedParameters.add("x5c");
171                }
172
173                if (getKeyID() != null) {
174                        includedParameters.add("kid");
175                }
176
177                return includedParameters;
178        }
179
180
181        /**
182         * Parses a JWS header from the specified JSON object.
183         *
184         * @param json The JSON object to parse. Must not be {@code null}.
185         *
186         * @return The JWS header.
187         *
188         * @throws ParseException If the specified JSON object doesn't 
189         *                        represent a valid JWS header.
190         */
191        public static JWSHeader parse(final JSONObject json)
192                throws ParseException {
193
194                // Get the "alg" parameter
195                Algorithm alg = Header.parseAlgorithm(json);
196
197                if (! (alg instanceof JWSAlgorithm)) {
198                        throw new ParseException("The algorithm \"alg\" header parameter must be for signatures", 0);
199                }
200
201                // Create a minimal header
202                JWSHeader h = new JWSHeader((JWSAlgorithm)alg);
203
204                // Parse optional + custom parameters
205                for (final String name: json.keySet()) {
206
207                        if (name.equals("alg")) {
208                                continue; // Skip
209                        } else if (name.equals("typ")) {
210                                h.setType(new JOSEObjectType(JSONObjectUtils.getString(json, name)));
211                        } else if (name.equals("cty")) {
212                                h.setContentType(JSONObjectUtils.getString(json, name)); 
213                        } else if (name.equals("crit")) {
214                                h.setCriticalHeaders(new HashSet<String>(JSONObjectUtils.getStringList(json, name)));
215                        } else if (name.equals("jku")) {
216                                h.setJWKURL(JSONObjectUtils.getURL(json, name));
217                        } else if (name.equals("jwk")) {
218                                h.setJWK(JWK.parse(JSONObjectUtils.getJSONObject(json, name)));
219                        } else if (name.equals("x5u")) {
220                                h.setX509CertURL(JSONObjectUtils.getURL(json, name));
221                        } else if (name.equals("x5t")) {
222                                h.setX509CertThumbprint(new Base64URL(JSONObjectUtils.getString(json, name)));
223                        } else if (name.equals("x5c")) {
224                                h.setX509CertChain(X509CertChainUtils.parseX509CertChain(JSONObjectUtils.getJSONArray(json, name)));
225                        } else if (name.equals("kid")) {
226                                h.setKeyID(JSONObjectUtils.getString(json, name));
227                        } else {
228                                h.setCustomParameter(name, json.get(name));
229                        }
230                }
231
232                return h;
233        }
234
235
236        /**
237         * Parses a JWS header from the specified JSON object string.
238         *
239         * @param s The JSON object string to parse. Must not be {@code null}.
240         *
241         * @return The JWS header.
242         *
243         * @throws ParseException If the specified JSON object string doesn't 
244         *                        represent a valid JWS header.
245         */
246        public static JWSHeader parse(final String s)
247                throws ParseException {
248
249                JSONObject jsonObject = JSONObjectUtils.parseJSONObject(s);
250
251                return parse(jsonObject);
252        }
253
254
255        /**
256         * Parses a JWS header from the specified Base64URL.
257         *
258         * @param base64URL The Base64URL to parse. Must not be {@code null}.
259         *
260         * @return The JWS header.
261         *
262         * @throws ParseException If the specified Base64URL doesn't represent
263         *                        a valid JWS header.
264         */
265        public static JWSHeader parse(final Base64URL base64URL)
266                throws ParseException {
267
268                JWSHeader header = parse(base64URL.decodeToString());
269                header.setParsedBase64URL(base64URL);
270                return header;
271        }
272}