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}