001 package com.nimbusds.jose; 002 003 004 import java.text.ParseException; 005 import java.util.Collections; 006 import java.util.HashMap; 007 import java.util.Map; 008 009 import net.minidev.json.JSONObject; 010 011 import com.nimbusds.jose.util.Base64URL; 012 import com.nimbusds.jose.util.JSONObjectUtils; 013 014 015 /** 016 * The base abstract class for plaintext, JSON Web Signature (JWS) and JSON Web 017 * Encryption (JWE) headers. 018 * 019 * <p>The header may also carry {@link #setCustomParameters custom parameters}; 020 * these will be serialised and parsed along the reserved ones. 021 * 022 * @author Vladimir Dzhuvinov 023 * @version $version$ (2013-04-15) 024 */ 025 public abstract class Header implements ReadOnlyHeader { 026 027 028 /** 029 * The algorithm ({@code alg}) parameter. 030 */ 031 final protected Algorithm alg; 032 033 034 /** 035 * The JOSE object type ({@code typ}) parameter. 036 */ 037 private JOSEObjectType typ; 038 039 040 /** 041 * The content type ({@code cty}) parameter. 042 */ 043 private String cty; 044 045 046 /** 047 * Custom header parameters. 048 */ 049 private Map<String,Object> customParameters = new HashMap<String,Object>(); 050 051 052 /** 053 * The original parsed Base64URL, {@code null} if the header was 054 * created from scratch. 055 */ 056 private Base64URL parsedBase64URL; 057 058 059 /** 060 * Creates a new header with the specified algorithm ({@code alg}) 061 * parameter. 062 * 063 * @param alg The algorithm parameter. Must not be {@code null}. 064 */ 065 protected Header(final Algorithm alg) { 066 067 if (alg == null) { 068 throw new IllegalArgumentException("The algorithm \"alg\" header parameter must not be null"); 069 } 070 071 this.alg = alg; 072 } 073 074 075 @Override 076 public JOSEObjectType getType() { 077 078 return typ; 079 } 080 081 082 /** 083 * Sets the type ({@code typ}) parameter. 084 * 085 * @param typ The type parameter, {@code null} if not specified. 086 */ 087 public void setType(final JOSEObjectType typ) { 088 089 this.typ = typ; 090 } 091 092 093 @Override 094 public String getContentType() { 095 096 return cty; 097 } 098 099 100 /** 101 * Sets the content type ({@code cty}) parameter. 102 * 103 * @param cty The content type parameter, {@code null} if not specified. 104 */ 105 public void setContentType(final String cty) { 106 107 this.cty = cty; 108 } 109 110 111 @Override 112 public Object getCustomParameter(final String name) { 113 114 return customParameters.get(name); 115 } 116 117 118 /** 119 * Sets a custom (non-reserved) parameter. Callers and extending classes 120 * should ensure the parameter name doesn't match a reserved parameter 121 * name. 122 * 123 * @param name The name of the custom parameter. Must not match a 124 * reserved parameter name and must not be {@code null}. 125 * @param value The value of the custom parameter, should map to a valid 126 * JSON entity, {@code null} if not specified. 127 */ 128 protected void setCustomParameter(final String name, final Object value) { 129 130 customParameters.put(name, value); 131 } 132 133 134 @Override 135 public Map<String,Object> getCustomParameters() { 136 137 return Collections.unmodifiableMap(customParameters); 138 } 139 140 141 /** 142 * Sets the custom (non-reserved) parameters. The values must be 143 * serialisable to a JSON entity, otherwise will be ignored. 144 * 145 * @param customParameters The custom parameters, empty map or 146 * {@code null} if none. 147 */ 148 public void setCustomParameters(final Map<String,Object> customParameters) { 149 150 if (customParameters == null) { 151 return; 152 } 153 154 this.customParameters = customParameters; 155 } 156 157 158 /** 159 * Sets the original parsed Base64URL used to create this header. 160 * 161 * @param parsedBase64URL The parsed Base64URL, {@code null} if the 162 * header was created from scratch. 163 */ 164 protected void setParsedBase64URL(final Base64URL parsedBase64URL) { 165 166 this.parsedBase64URL = parsedBase64URL; 167 } 168 169 170 @Override 171 public JSONObject toJSONObject() { 172 173 // Include custom parameters, they will be overwritten if their 174 // names match specified reserved ones 175 JSONObject o = new JSONObject(customParameters); 176 177 // Alg is always defined 178 o.put("alg", alg.toString()); 179 180 if (typ != null) { 181 o.put("typ", typ.toString()); 182 } 183 184 if (cty != null) { 185 o.put("cty", cty); 186 } 187 188 return o; 189 } 190 191 192 @Override 193 public String toString() { 194 195 return toJSONObject().toString(); 196 } 197 198 199 @Override 200 public Base64URL toBase64URL() { 201 202 if (parsedBase64URL == null) { 203 204 // Header was created from scratch, return new Base64URL 205 return Base64URL.encode(toString()); 206 207 } else { 208 209 // Header was parsed, return original Base64URL 210 return parsedBase64URL; 211 } 212 } 213 214 215 /** 216 * Parses an algorithm ({@code alg}) parameter from the specified 217 * header JSON object. Intended for initial parsing of plain, JWS and 218 * JWE headers. 219 * 220 * <p>The algorithm type (none, JWS or JWE) is determined by inspecting 221 * the algorithm name for "none" and the presence of an "enc" parameter. 222 * 223 * @param json The JSON object to parse. Must not be {@code null}. 224 * 225 * @return The algorithm, an instance of {@link Algorithm#NONE}, 226 * {@link JWSAlgorithm} or {@link JWEAlgorithm}. 227 * 228 * @throws ParseException If the {@code alg} parameter couldn't be 229 * parsed. 230 */ 231 public static Algorithm parseAlgorithm(final JSONObject json) 232 throws ParseException { 233 234 String algName = JSONObjectUtils.getString(json, "alg"); 235 236 // Infer algorithm type 237 238 if (algName.equals(Algorithm.NONE.getName())) { 239 // Plain 240 return Algorithm.NONE; 241 } else if (json.containsKey("enc")) { 242 // JWE 243 return JWEAlgorithm.parse(algName); 244 } else { 245 // JWS 246 return JWSAlgorithm.parse(algName); 247 } 248 } 249 250 251 /** 252 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 253 * from the specified JSON object. 254 * 255 * @param json The JSON object to parse. Must not be {@code null}. 256 * 257 * @return The header. 258 * 259 * @throws ParseException If the specified JSON object doesn't 260 * represent a valid header. 261 */ 262 public static Header parse(final JSONObject json) 263 throws ParseException { 264 265 Algorithm alg = parseAlgorithm(json); 266 267 if (alg.equals(Algorithm.NONE)) { 268 269 return PlainHeader.parse(json); 270 271 } else if (alg instanceof JWSAlgorithm) { 272 273 return JWSHeader.parse(json); 274 275 } else if (alg instanceof JWEAlgorithm) { 276 277 return JWEHeader.parse(json); 278 279 } else { 280 281 throw new AssertionError("Unexpected algorithm type: " + alg); 282 } 283 } 284 }