1 /*
2 * The contents of this file are subject to the terms of the Common Development and
3 * Distribution License (the License). You may not use this file except in compliance with the
4 * License.
5 *
6 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7 * specific language governing permission and limitations under the License.
8 *
9 * When distributing Covered Software, include this CDDL Header Notice in each file and include
10 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11 * Header, with the fields enclosed by brackets [] replaced by your own identifying
12 * information: "Portions copyright [year] [name of copyright owner]".
13 *
14 * Copyright 2013-2016 ForgeRock AS.
15 */
16
17 package org.forgerock.json.jose.jws;
18
19 import static org.forgerock.json.jose.jws.JwsHeaderKey.CRIT;
20 import static org.forgerock.json.jose.jws.JwsHeaderKey.CTY;
21 import static org.forgerock.json.jose.jws.JwsHeaderKey.JKU;
22 import static org.forgerock.json.jose.jws.JwsHeaderKey.JWK;
23 import static org.forgerock.json.jose.jws.JwsHeaderKey.KID;
24 import static org.forgerock.json.jose.jws.JwsHeaderKey.X5C;
25 import static org.forgerock.json.jose.jws.JwsHeaderKey.X5T;
26 import static org.forgerock.json.jose.jws.JwsHeaderKey.X5U;
27 import static org.forgerock.json.jose.jws.JwsHeaderKey.getHeaderKey;
28
29 import java.net.MalformedURLException;
30 import java.net.URL;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Map;
34
35 import org.forgerock.json.jose.exceptions.JwtRuntimeException;
36 import org.forgerock.json.jose.jwe.CompressionAlgorithm;
37 import org.forgerock.json.jose.jwk.JWK;
38 import org.forgerock.json.jose.jwt.JwtHeader;
39 import org.forgerock.json.jose.utils.Utils;
40 import org.forgerock.util.encode.Base64;
41
42 /**
43 * A base implementation for the common security header parameters shared by the JWS and JWE headers.
44 *
45 * @since 2.0.0
46 */
47 public abstract class JwtSecureHeader extends JwtHeader {
48 private static final String COMPRESSION_ALGORITHM_HEADER_KEY = "zip";
49
50 /**
51 * Constructs a new, empty JwtSecureHeader.
52 */
53 public JwtSecureHeader() {
54 }
55
56 /**
57 * Constructs a new JwtSecureHeader, with its parameters set to the contents of the given Map.
58 *
59 * @param headers A Map containing the parameters to be set in the header.
60 */
61 public JwtSecureHeader(Map<String, Object> headers) {
62 setParameters(headers);
63 }
64
65 /**
66 * Sets the JWK Set URL header parameter for this JWS.
67 * <p>
68 * A URI that refers to a resource for a set of JSON-encoded public keys, one of which corresponds to the key used
69 * to digitally sign the JWS.
70 * <p>
71 * The keys MUST be encoded as a JSON Web Key Set (JWK Set).
72 * <p>
73 * The protocol used to acquire the resource MUST provide integrity protection and the identity of the server MUST
74 * be validated.
75 *
76 * @param jwkSetUrl The JWK Set URL.
77 */
78 public void setJwkSetUrl(URL jwkSetUrl) {
79 put(JKU.value(), new String(jwkSetUrl.toString()));
80 }
81
82 /**
83 * Gets the JWK Set URL header parameter for this JWS.
84 *
85 * @return The JWK Set URL.
86 */
87 public URL getJwkSetUrl() {
88 try {
89 String url = get(JKU.value()).asString();
90 return url != null
91 ? new URL(url)
92 : null;
93 } catch (MalformedURLException e) {
94 throw new JwtRuntimeException(e);
95 }
96 }
97
98 /**
99 * 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 }