001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2013-2016 ForgeRock AS. 015 */ 016 017package org.forgerock.json.jose.jws; 018 019import static org.forgerock.json.jose.jws.JwsHeaderKey.CRIT; 020import static org.forgerock.json.jose.jws.JwsHeaderKey.CTY; 021import static org.forgerock.json.jose.jws.JwsHeaderKey.JKU; 022import static org.forgerock.json.jose.jws.JwsHeaderKey.JWK; 023import static org.forgerock.json.jose.jws.JwsHeaderKey.KID; 024import static org.forgerock.json.jose.jws.JwsHeaderKey.X5C; 025import static org.forgerock.json.jose.jws.JwsHeaderKey.X5T; 026import static org.forgerock.json.jose.jws.JwsHeaderKey.X5U; 027import static org.forgerock.json.jose.jws.JwsHeaderKey.getHeaderKey; 028 029import java.net.MalformedURLException; 030import java.net.URL; 031import java.util.ArrayList; 032import java.util.List; 033import java.util.Map; 034 035import org.forgerock.json.jose.exceptions.JwtRuntimeException; 036import org.forgerock.json.jose.jwe.CompressionAlgorithm; 037import org.forgerock.json.jose.jwk.JWK; 038import org.forgerock.json.jose.jwt.JwtHeader; 039import org.forgerock.json.jose.utils.Utils; 040import org.forgerock.util.encode.Base64; 041 042/** 043 * A base implementation for the common security header parameters shared by the JWS and JWE headers. 044 * 045 * @since 2.0.0 046 */ 047public abstract class JwtSecureHeader extends JwtHeader { 048 private static final String COMPRESSION_ALGORITHM_HEADER_KEY = "zip"; 049 050 /** 051 * Constructs a new, empty JwtSecureHeader. 052 */ 053 public JwtSecureHeader() { 054 } 055 056 /** 057 * Constructs a new JwtSecureHeader, with its parameters set to the contents of the given Map. 058 * 059 * @param headers A Map containing the parameters to be set in the header. 060 */ 061 public JwtSecureHeader(Map<String, Object> headers) { 062 setParameters(headers); 063 } 064 065 /** 066 * Sets the JWK Set URL header parameter for this JWS. 067 * <p> 068 * A URI that refers to a resource for a set of JSON-encoded public keys, one of which corresponds to the key used 069 * to digitally sign the JWS. 070 * <p> 071 * The keys MUST be encoded as a JSON Web Key Set (JWK Set). 072 * <p> 073 * The protocol used to acquire the resource MUST provide integrity protection and the identity of the server MUST 074 * be validated. 075 * 076 * @param jwkSetUrl The JWK Set URL. 077 */ 078 public void setJwkSetUrl(URL jwkSetUrl) { 079 put(JKU.value(), new String(jwkSetUrl.toString())); 080 } 081 082 /** 083 * Gets the JWK Set URL header parameter for this JWS. 084 * 085 * @return The JWK Set URL. 086 */ 087 public URL getJwkSetUrl() { 088 try { 089 String url = get(JKU.value()).asString(); 090 return url != null 091 ? new URL(url) 092 : null; 093 } catch (MalformedURLException e) { 094 throw new JwtRuntimeException(e); 095 } 096 } 097 098 /** 099 * Sets the JSON Web Key header parameter for this JWS. 100 * <p> 101 * The public key that corresponds to the key used to digitally sign the JWS. This key is represented as a JSON Web 102 * Key (JWK). 103 * 104 * @param jsonWebKey The JSON Web Key. 105 */ 106 public void setJsonWebKey(JWK jsonWebKey) { 107 put(JWK.value(), jsonWebKey); 108 } 109 110 /** 111 * Gets the JSON Web Key header parameter for this JWS. 112 * 113 * @return The JSON Web Key. 114 */ 115 public JWK getJsonWebKey() { 116 return (JWK) get(JWK.value()).getObject(); 117 } 118 119 /** 120 * Sets the X.509 URL header parameter for this JWS. 121 * <p> 122 * A URI that refers to a resource for the X.509 public key certificate or certificate chain corresponding to the 123 * key used to digitally sign the JWS. 124 * <p> 125 * The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the 126 * first certificate. This MAY be followed by additional certificates, with each subsequent certificate being the 127 * one used to certify the previous one. 128 * <p> 129 * The protocol used to acquire the resource MUST provide integrity protection and the identity of the server MUST 130 * be validated. 131 * 132 * @param x509Url The X.509 URL. 133 */ 134 public void setX509Url(URL x509Url) { 135 put(X5U.value(), new String(x509Url.toString())); 136 } 137 138 /** 139 * Gets the X.509 URL header parameter for this JWS. 140 * 141 * @return The X.509 URL. 142 */ 143 public URL getX509Url() { 144 try { 145 String url = get(X5U.value()).asString(); 146 return url != null 147 ? new URL(url) 148 : null; 149 } catch (MalformedURLException e) { 150 throw new JwtRuntimeException(e); 151 } 152 } 153 154 /** 155 * Sets the X.509 Certificate Thumbprint header parameter for this JWS. 156 * <p> 157 * A base64url encoded SHA-1 thumbprint (a.k.a. digest) of the DER encoding of the X.509 certificate corresponding 158 * to the key used to digitally sign the JWS. 159 * <p> 160 * This method will perform the base64url encoding so the x509CertificateThumbprint must be the SHA-1 digest. 161 * 162 * @param x509CertificateThumbprint The X.509 Certificate Thumbprint. 163 */ 164 public void setX509CertificateThumbprint(String x509CertificateThumbprint) { 165 put(X5T.value(), Utils.base64urlEncode(x509CertificateThumbprint)); 166 } 167 168 /** 169 * Gets the X.509 Certificate Thumbprint header parameter for this JWS. 170 * 171 * @return The X.509 Certificate Thumbprint. 172 */ 173 public String getX509CertificateThumbprint() { 174 return get(X5T.value()).asString(); 175 } 176 177 /** 178 * Sets the X.509 Certificate Chain header parameter for this JWS. 179 * <p> 180 * Contains the list of X.509 public key certificate or certificate chain corresponding to the key used to 181 * digitally sign the JWS. 182 * Each entry in the list is a base64 encoded DER PKIX certificate value. 183 * This method will perform the base64 encoding of each entry so the entries in the list must be the DER PKIX 184 * certificate values. 185 * <p> 186 * The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the 187 * first certificate. This MAY be followed by additional certificates, with each subsequent certificate being the 188 * one used to certify the previous one. 189 * <p> 190 * 191 * @param x509CertificateChain The X.509 Certificate Chain. 192 */ 193 public void setX509CertificateChain(List<String> x509CertificateChain) { 194 List<String> encodedCertChain = new ArrayList<>(); 195 for (String x509Cert : x509CertificateChain) { 196 encodedCertChain.add(Base64.encode(x509Cert.getBytes(Utils.CHARSET))); 197 } 198 put(X5C.value(), encodedCertChain); 199 } 200 201 /** 202 * Gets the X.509 Certificate Chain header parameter for this JWS. 203 * 204 * @return The X.509 Certificate Chain. 205 */ 206 public List<String> getX509CertificateChain() { 207 return get(X5C.value()).asList(String.class); 208 } 209 210 /** 211 * Sets the Key ID header parameter for this JWS. 212 * <p> 213 * Indicates which key was used to secure the JWS, allowing originators to explicitly signal a change of key to 214 * recipients. 215 * 216 * @param keyId The Key ID. 217 */ 218 public void setKeyId(String keyId) { 219 put(KID.value(), keyId); 220 } 221 222 /** 223 * Gets the Key ID header parameter for this JWS. 224 * 225 * @return The Key ID. 226 */ 227 public String getKeyId() { 228 return get(KID.value()).asString(); 229 } 230 231 /** 232 * Sets the content type header parameter for this JWS. 233 * <p> 234 * Declares the type of the secured content (the Payload). 235 * 236 * @param contentType The content type of this JWS' payload. 237 */ 238 public void setContentType(String contentType) { 239 put(CTY.value(), contentType); 240 } 241 242 /** 243 * Gets the content type header parameter for this JWS. 244 * 245 * @return The content type of this JWS' payload. 246 */ 247 public String getContentType() { 248 return get(CTY.value()).asString(); 249 } 250 251 /** 252 * Sets the critical header parameters for this JWS. 253 * <p> 254 * This header parameter indicates that extensions to the JWS specification are being used that MUST be understood 255 * and processed. 256 * <p> 257 * The criticalHeaders parameter cannot be an empty list. 258 * 259 * @param criticalHeaders A List of the critical parameters. 260 */ 261 public void setCriticalHeaders(List<String> criticalHeaders) { 262 if (criticalHeaders != null && criticalHeaders.isEmpty()) { 263 throw new JwtRuntimeException("Critical Headers parameter cannot be an empty list"); 264 } 265 put(CRIT.value(), criticalHeaders); 266 } 267 268 /** 269 * Gets the critical header parameters for this JWS. 270 * 271 * @return A List of the critical parameters. 272 */ 273 public List<String> getCriticalHeaders() { 274 return get(CRIT.value()).asList(String.class); 275 } 276 277 /** 278 * {@inheritDoc} 279 */ 280 @SuppressWarnings("unchecked") 281 @Override 282 public void setParameter(String key, Object value) { 283 JwsHeaderKey headerKey = getHeaderKey(key.toUpperCase()); 284 285 switch (headerKey) { 286 case JKU: { 287 checkValueIsOfType(value, URL.class); 288 setJwkSetUrl((URL) value); 289 break; 290 } 291 case JWK: { 292 checkValueIsOfType(value, JWK.class); 293 setJsonWebKey((JWK) value); 294 break; 295 } 296 case X5U: { 297 checkValueIsOfType(value, URL.class); 298 setX509Url((URL) value); 299 break; 300 } 301 case X5T: { 302 checkValueIsOfType(value, String.class); 303 setX509CertificateThumbprint((String) value); 304 break; 305 } 306 case X5C: { 307 checkValueIsOfType(value, List.class); 308 checkListValuesAreOfType((List<?>) value, String.class); 309 setX509CertificateChain((List<String>) value); 310 break; 311 } 312 case KID: { 313 checkValueIsOfType(value, String.class); 314 setKeyId((String) value); 315 break; 316 } 317 case CTY: { 318 checkValueIsOfType(value, String.class); 319 setContentType((String) value); 320 break; 321 } 322 case CRIT: { 323 checkValueIsOfType(value, List.class); 324 checkListValuesAreOfType((List<?>) value, String.class); 325 setCriticalHeaders((List<String>) value); 326 break; 327 } 328 default: { 329 super.setParameter(key, value); 330 } 331 } 332 } 333 334 /** 335 * {@inheritDoc} 336 */ 337 @Override 338 public Object getParameter(String key) { 339 JwsHeaderKey headerKey = getHeaderKey(key.toUpperCase()); 340 341 Object value; 342 343 switch (headerKey) { 344 case JKU: { 345 value = getJwkSetUrl(); 346 break; 347 } 348 case JWK: { 349 value = getJsonWebKey(); 350 break; 351 } 352 case X5U: { 353 value = getX509Url(); 354 break; 355 } 356 case X5T: { 357 value = getX509CertificateThumbprint(); 358 break; 359 } 360 case X5C: { 361 value = getX509CertificateChain(); 362 break; 363 } 364 case KID: { 365 value = getKeyId(); 366 break; 367 } 368 case CTY: { 369 value = getContentType(); 370 break; 371 } 372 case CRIT: { 373 value = getCriticalHeaders(); 374 break; 375 } 376 default: { 377 value = super.getParameter(key); 378 } 379 } 380 381 return value; 382 } 383 384 /** 385 * Sets the Compression Algorithm header parameter for this JWE. 386 * <p> 387 * If present, the value of the Compression Algorithm header parameter MUST be CompressionAlgorithm constant DEF. 388 * 389 * @param compressionAlgorithm The Compression Algorithm. 390 */ 391 public void setCompressionAlgorithm(CompressionAlgorithm compressionAlgorithm) { 392 put(COMPRESSION_ALGORITHM_HEADER_KEY, compressionAlgorithm.toString()); 393 } 394 395 /** 396 * Gets the Compression Algorithm header parameter for this JWE. 397 * 398 * @return The Compression Algorithm. 399 */ 400 public CompressionAlgorithm getCompressionAlgorithm() { 401 String compressionAlgorithm = get(COMPRESSION_ALGORITHM_HEADER_KEY).asString(); 402 if (compressionAlgorithm == null) { 403 return CompressionAlgorithm.NONE; 404 } else { 405 return CompressionAlgorithm.valueOf(compressionAlgorithm); 406 } 407 } 408 409}