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