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}