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}