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}