001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.http.header;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.security.*;
022import java.util.*;
023import java.util.function.*;
024
025/**
026 * Represents an HTTP <c>Content-Security-Policy</c> (or <c>Content-Security-Policy-Report-Only</c>) response header.
027 *
028 * <p>
029 * A Content Security Policy lets a server declare which content sources a browser may load and execute for a page,
030 * mitigating cross-site-scripting (XSS) and data-injection attacks.  This bean carries an already-rendered policy
031 * value; use {@link Builder} to assemble the value from typed directives.
032 *
033 * <h5 class='section'>Example:</h5>
034 * <p class='bjava'>
035 *    <jc>// Build a static policy and emit it as a response header.</jc>
036 *    ContentSecurityPolicy <jv>csp</jv> = ContentSecurityPolicy.<jsm>create</jsm>()
037 *       .defaultSrc(ContentSecurityPolicy.Builder.<jsf>SELF</jsf>)
038 *       .objectSrc(ContentSecurityPolicy.Builder.<jsf>NONE</jsf>)
039 *       .toHeader();
040 *
041 *    <jc>// =&gt; "Content-Security-Policy: default-src 'self'; object-src 'none'"</jc>
042 * </p>
043 *
044 * <p>
045 * Because this is an {@link HttpStringHeader}, an instance can be added directly to the REST server's
046 * <c>defaultResponseHeaders</c> {@link HttpHeaderList} (e.g. via a <c><ja>@Bean</ja>(name=<js>"defaultResponseHeaders"</js>)</c>
047 * factory method) and is emitted through the normal response-header path.
048 *
049 * <h5 class='section'>See Also:</h5><ul>
050 *    <li class='jc'>{@link Builder}
051 *    <li class='extlink'><a class='doclink' href='https://www.w3.org/TR/CSP3/'>Content Security Policy Level 3 (W3C WD)</a>
052 *    <li class='extlink'><a class='doclink' href='https://www.w3.org/TR/CSP2/'>Content Security Policy Level 2 (W3C Rec)</a>
053 * </ul>
054 *
055 * @since 10.0.0
056 */
057@SuppressWarnings({
058   "java:S115" // NAME / REPORT_ONLY_NAME mirror the external HTTP header literal exactly.
059})
060public class ContentSecurityPolicy extends HttpStringHeader {
061
062   /** The enforcing header name: {@value}. */
063   public static final String NAME = "Content-Security-Policy";
064
065   /** The observe-only header name: {@value}. */
066   public static final String REPORT_ONLY_NAME = "Content-Security-Policy-Report-Only";
067
068   /**
069    * Constructor with an eager value (enforcing {@value #NAME} header).
070    *
071    * @param value The header value. May be <jk>null</jk>.
072    */
073   public ContentSecurityPolicy(String value) {
074      super(NAME, value);
075   }
076
077   /**
078    * Constructor with a lazy value supplier (enforcing {@value #NAME} header).
079    *
080    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
081    */
082   public ContentSecurityPolicy(Supplier<String> valueSupplier) {
083      super(NAME, valueSupplier);
084   }
085
086   /**
087    * Constructor allowing the header name to be chosen (enforcing vs. report-only).
088    *
089    * @param name The header name. Must not be <jk>null</jk>.
090    * @param value The header value. May be <jk>null</jk>.
091    */
092   protected ContentSecurityPolicy(String name, String value) {
093      super(name, value);
094   }
095
096   /**
097    * Constructor allowing the header name to be chosen, with a lazy value supplier.
098    *
099    * @param name The header name. Must not be <jk>null</jk>.
100    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
101    */
102   protected ContentSecurityPolicy(String name, Supplier<String> valueSupplier) {
103      super(name, valueSupplier);
104   }
105
106   /**
107    * Static factory method for an enforcing ({@value #NAME}) header with an eager value.
108    *
109    * @param value The header value. May be <jk>null</jk>.
110    * @return A new instance. Never <jk>null</jk>.
111    */
112   public static ContentSecurityPolicy of(String value) {
113      return new ContentSecurityPolicy(value);
114   }
115
116   /**
117    * Static factory method for an enforcing ({@value #NAME}) header with a lazy value supplier.
118    *
119    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
120    * @return A new instance. Never <jk>null</jk>.
121    */
122   public static ContentSecurityPolicy of(Supplier<String> valueSupplier) {
123      return new ContentSecurityPolicy(valueSupplier);
124   }
125
126   /**
127    * Static factory method for an observe-only ({@value #REPORT_ONLY_NAME}) header with an eager value.
128    *
129    * <p>
130    * Browsers report violations against this header but do not block anything, letting operators tune a policy
131    * before enforcing it.
132    *
133    * @param value The header value. May be <jk>null</jk>.
134    * @return A new instance. Never <jk>null</jk>.
135    */
136   public static ContentSecurityPolicy ofReportOnly(String value) {
137      return new ContentSecurityPolicy(REPORT_ONLY_NAME, value);
138   }
139
140   /**
141    * Static factory method for an observe-only ({@value #REPORT_ONLY_NAME}) header with a lazy value supplier.
142    *
143    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
144    * @return A new instance. Never <jk>null</jk>.
145    */
146   public static ContentSecurityPolicy ofReportOnly(Supplier<String> valueSupplier) {
147      return new ContentSecurityPolicy(REPORT_ONLY_NAME, valueSupplier);
148   }
149
150   /**
151    * Creates a new empty {@link Builder}.
152    *
153    * @return A new builder. Never <jk>null</jk>.
154    */
155   public static Builder create() {
156      return Builder.create();
157   }
158
159   /**
160    * Creates a {@link Builder} pre-populated with the nonce-based <i>strict starter</i> preset.
161    *
162    * @return A new builder pre-populated with the strict-starter directives. Never <jk>null</jk>.
163    * @see Builder#strictStarter()
164    */
165   public static Builder strictStarter() {
166      return Builder.strictStarter();
167   }
168
169   //------------------------------------------------------------------------------------------------------------------
170   // Inner types
171   //------------------------------------------------------------------------------------------------------------------
172
173   /**
174    * Fluent builder for assembling a <c>Content-Security-Policy</c> header value.
175    *
176    * <p>
177    * Composes the directives defined by
178    * <a class='doclink' href='https://www.w3.org/TR/CSP3/'>CSP Level 3</a> into the {@code directive source source; ...}
179    * wire format expected by the {@code Content-Security-Policy} response header.  Typed setters cover the common
180    * directives; {@link #directive(String, String...)} is a generic escape hatch so new directives never require an API
181    * change.  Setting the same directive more than once <b>replaces</b> the previous source list.
182    *
183    * <h5 class='section'>Example:</h5>
184    * <p class='bjava'>
185    *    String <jv>value</jv> = ContentSecurityPolicy.<jsm>create</jsm>()
186    *       .defaultSrc(<jsf>SELF</jsf>)
187    *       .imgSrc(<jsf>SELF</jsf>, <js>"data:"</js>)
188    *       .objectSrc(<jsf>NONE</jsf>)
189    *       .build();
190    *    <jc>// =&gt; "default-src 'self'; img-src 'self' data:; object-src 'none'"</jc>
191    * </p>
192    *
193    * <h5 class='section'>Nonces:</h5>
194    * <p>
195    * Source helper {@link #nonce(String)} renders a <c>'nonce-&lt;token&gt;'</c> source expression.  For per-request
196    * nonces (where the actual token is not known until the response is rendered), use {@link #NONCE_PLACEHOLDER} as the
197    * token and have the REST layer rewrite it per-response via {@link #resolveNonce(String, String)}; the same nonce is
198    * then stamped onto the inline <c>&lt;script&gt;</c>/<c>&lt;style&gt;</c> tags the serializer emits.
199    *
200    * <h5 class='section'>See Also:</h5><ul>
201    *    <li class='jc'>{@link ContentSecurityPolicy}
202    *    <li class='extlink'><a class='doclink' href='https://www.w3.org/TR/CSP3/'>Content Security Policy Level 3 (W3C WD)</a>
203    * </ul>
204    *
205    * @since 10.0.0
206    */
207   public static class Builder {
208
209      //--------------------------------------------------------------------------------------------------------------
210      // Source-expression keyword helpers
211      //--------------------------------------------------------------------------------------------------------------
212
213      /** The <c>'self'</c> source-expression keyword. */
214      public static final String SELF = "'self'";
215
216      /** The <c>'none'</c> source-expression keyword. */
217      public static final String NONE = "'none'";
218
219      /** The <c>'unsafe-inline'</c> source-expression keyword. */
220      public static final String UNSAFE_INLINE = "'unsafe-inline'";
221
222      /** The <c>'unsafe-eval'</c> source-expression keyword. */
223      public static final String UNSAFE_EVAL = "'unsafe-eval'";
224
225      /** The <c>'strict-dynamic'</c> source-expression keyword. */
226      public static final String STRICT_DYNAMIC = "'strict-dynamic'";
227
228      /**
229       * The placeholder token used in nonce sources when the actual per-request nonce is not yet known.
230       *
231       * <p>
232       * {@link #strictStarter()} emits {@code 'nonce-{nonce}'}; the REST layer substitutes the real nonce per-response
233       * via {@link #resolveNonce(String, String)}.
234       */
235      public static final String NONCE_PLACEHOLDER = "{nonce}";
236
237      /**
238       * Renders a <c>'nonce-&lt;token&gt;'</c> source expression.
239       *
240       * @param token The base64 nonce value (or {@link #NONCE_PLACEHOLDER} for deferred substitution). Must not be <jk>null</jk> or blank.
241       * @return The source expression. Never <jk>null</jk>.
242       * @throws IllegalArgumentException If {@code token} is <jk>null</jk> or blank.
243       */
244      public static String nonce(String token) {
245         reqnn("token", token);
246         if (token.trim().isEmpty())
247            throw iaex("nonce token must not be blank");
248         return "'nonce-" + token + "'";
249      }
250
251      /**
252       * Renders a scheme source expression, ensuring a single trailing colon (e.g. <js>"https"</js> =&gt; <c>https:</c>).
253       *
254       * @param scheme The scheme (with or without a trailing colon). Must not be <jk>null</jk> or blank.
255       * @return The scheme source expression. Never <jk>null</jk>.
256       * @throws IllegalArgumentException If {@code scheme} is <jk>null</jk> or blank.
257       */
258      public static String scheme(String scheme) {
259         reqnn("scheme", scheme);
260         var s = scheme.trim();
261         if (s.isEmpty())
262            throw iaex("scheme must not be blank");
263         return s.endsWith(":") ? s : s + ":";
264      }
265
266      /**
267       * Renders a <c>'sha256-&lt;hash&gt;'</c> (or sha384/sha512) source expression.
268       *
269       * @param algorithm One of <js>"sha256"</js>, <js>"sha384"</js>, or <js>"sha512"</js>. Must not be <jk>null</jk> or blank.
270       * @param base64Hash The base64-encoded hash of the inline block. Must not be <jk>null</jk> or blank.
271       * @return The hash source expression. Never <jk>null</jk>.
272       * @throws IllegalArgumentException If either argument is <jk>null</jk> or blank.
273       */
274      public static String hash(String algorithm, String base64Hash) {
275         reqnn("algorithm", algorithm);
276         reqnn("base64Hash", base64Hash);
277         if (algorithm.trim().isEmpty())
278            throw iaex("hash algorithm must not be blank");
279         if (base64Hash.trim().isEmpty())
280            throw iaex("hash value must not be blank");
281         return "'" + algorithm.trim() + "-" + base64Hash.trim() + "'";
282      }
283
284      private static final SecureRandom SECURE_RANDOM = new SecureRandom();
285
286      /**
287       * Mints a fresh, cryptographically-random CSP nonce.
288       *
289       * <p>
290       * Generates 128 bits (16 bytes) from {@link SecureRandom} encoded as an unpadded base64url string.  Each call
291       * returns a new value; a nonce must never be reused across responses.
292       *
293       * @return A new nonce token. Never <jk>null</jk>.
294       */
295      public static String generateNonce() {
296         var bytes = new byte[16];
297         SECURE_RANDOM.nextBytes(bytes);
298         return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
299      }
300
301      /**
302       * Replaces every occurrence of {@link #NONCE_PLACEHOLDER} in a rendered policy value with the supplied nonce.
303       *
304       * @param policyValue The rendered policy value (may contain {@link #NONCE_PLACEHOLDER}). May be <jk>null</jk>.
305       * @param nonce The per-request nonce token. Must not be <jk>null</jk>.
306       * @return The policy value with placeholders substituted, or <jk>null</jk> if {@code policyValue} was <jk>null</jk>.
307       * @throws IllegalArgumentException If {@code nonce} is <jk>null</jk>.
308       */
309      public static String resolveNonce(String policyValue, String nonce) {
310         reqnn("nonce", nonce);
311         if (policyValue == null)
312            return null;
313         return policyValue.replace(NONCE_PLACEHOLDER, nonce);
314      }
315
316      //--------------------------------------------------------------------------------------------------------------
317      // Instance
318      //--------------------------------------------------------------------------------------------------------------
319
320      private final Map<String,List<String>> directives = m();
321      private boolean reportOnly;
322
323      /**
324       * Creates a new empty builder.
325       *
326       * @return A new builder. Never <jk>null</jk>.
327       */
328      public static Builder create() {
329         return new Builder();
330      }
331
332      /**
333       * Creates a builder pre-populated with the nonce-based <i>strict starter</i> preset.
334       *
335       * <p>
336       * The preset is:
337       * <p class='bcode'>
338       *    default-src 'self';
339       *    script-src 'self' 'nonce-{nonce}';
340       *    style-src 'self' 'nonce-{nonce}';
341       *    img-src 'self' data:;
342       *    connect-src 'self';
343       *    object-src 'none';
344       *    base-uri 'self';
345       *    frame-ancestors 'self'
346       * </p>
347       *
348       * <p>
349       * The {@code {nonce}} tokens are {@link #NONCE_PLACEHOLDER}s; for the policy to function on Juneau-served HTML
350       * (which emits inline {@code <script>}/{@code <style>}) the REST layer must mint a per-request nonce, substitute
351       * it via {@link #resolveNonce(String, String)}, and stamp the same value onto the emitted inline tags.
352       *
353       * @return A new pre-populated builder. Never <jk>null</jk>.
354       */
355      public static Builder strictStarter() {
356         return create()
357            .defaultSrc(SELF)
358            .scriptSrc(SELF, nonce(NONCE_PLACEHOLDER))
359            .styleSrc(SELF, nonce(NONCE_PLACEHOLDER))
360            .imgSrc(SELF, "data:")
361            .connectSrc(SELF)
362            .objectSrc(NONE)
363            .baseUri(SELF)
364            .frameAncestors(SELF);
365      }
366
367      /**
368       * Sets the <c>default-src</c> directive.
369       *
370       * @param sources The source expressions. May be empty.
371       * @return This object.
372       */
373      public Builder defaultSrc(String...sources) {
374         return directive("default-src", sources);
375      }
376
377      /**
378       * Sets the <c>script-src</c> directive.
379       *
380       * @param sources The source expressions. May be empty.
381       * @return This object.
382       */
383      public Builder scriptSrc(String...sources) {
384         return directive("script-src", sources);
385      }
386
387      /**
388       * Sets the <c>style-src</c> directive.
389       *
390       * @param sources The source expressions. May be empty.
391       * @return This object.
392       */
393      public Builder styleSrc(String...sources) {
394         return directive("style-src", sources);
395      }
396
397      /**
398       * Sets the <c>img-src</c> directive.
399       *
400       * @param sources The source expressions. May be empty.
401       * @return This object.
402       */
403      public Builder imgSrc(String...sources) {
404         return directive("img-src", sources);
405      }
406
407      /**
408       * Sets the <c>connect-src</c> directive.
409       *
410       * @param sources The source expressions. May be empty.
411       * @return This object.
412       */
413      public Builder connectSrc(String...sources) {
414         return directive("connect-src", sources);
415      }
416
417      /**
418       * Sets the <c>font-src</c> directive.
419       *
420       * @param sources The source expressions. May be empty.
421       * @return This object.
422       */
423      public Builder fontSrc(String...sources) {
424         return directive("font-src", sources);
425      }
426
427      /**
428       * Sets the <c>object-src</c> directive.
429       *
430       * @param sources The source expressions. May be empty.
431       * @return This object.
432       */
433      public Builder objectSrc(String...sources) {
434         return directive("object-src", sources);
435      }
436
437      /**
438       * Sets the <c>base-uri</c> directive.
439       *
440       * @param sources The source expressions. May be empty.
441       * @return This object.
442       */
443      public Builder baseUri(String...sources) {
444         return directive("base-uri", sources);
445      }
446
447      /**
448       * Sets the <c>frame-ancestors</c> directive.
449       *
450       * @param sources The source expressions. May be empty.
451       * @return This object.
452       */
453      public Builder frameAncestors(String...sources) {
454         return directive("frame-ancestors", sources);
455      }
456
457      /**
458       * Sets the <c>report-uri</c> directive (deprecated in CSP3 but still widely supported).
459       *
460       * @param uris The collector URIs. May be empty.
461       * @return This object.
462       */
463      public Builder reportUri(String...uris) {
464         return directive("report-uri", uris);
465      }
466
467      /**
468       * Sets the <c>report-to</c> directive (the CSP3 reporting-group mechanism).
469       *
470       * @param groups The reporting-group names. May be empty.
471       * @return This object.
472       */
473      public Builder reportTo(String...groups) {
474         return directive("report-to", groups);
475      }
476
477      /**
478       * Generic escape hatch for setting any directive by name.
479       *
480       * <p>
481       * Replaces any previously-registered source list for the same directive while preserving its original position.
482       *
483       * @param name The directive name (e.g. <js>"worker-src"</js>). Must not be <jk>null</jk> or blank.
484       * @param sources The source expressions. May be empty or <jk>null</jk> (renders the bare directive name).
485       * @return This object.
486       * @throws IllegalArgumentException If {@code name} is <jk>null</jk> or blank.
487       */
488      public Builder directive(String name, String...sources) {
489         reqnn("name", name);
490         var n = name.trim();
491         if (n.isEmpty())
492            throw iaex("directive name must not be blank");
493         directives.put(n, sources == null ? l() : l(sources));
494         return this;
495      }
496
497      /**
498       * Flips the rendered header to the observe-only {@link ContentSecurityPolicy#REPORT_ONLY_NAME} variant.
499       *
500       * @return This object.
501       */
502      public Builder reportOnly() {
503         return reportOnly(true);
504      }
505
506      /**
507       * Sets whether the rendered header uses the observe-only {@link ContentSecurityPolicy#REPORT_ONLY_NAME} variant.
508       *
509       * @param value <jk>true</jk> to emit {@code Content-Security-Policy-Report-Only} instead of {@code Content-Security-Policy}.
510       * @return This object.
511       */
512      public Builder reportOnly(boolean value) {
513         reportOnly = value;
514         return this;
515      }
516
517      /**
518       * Returns <jk>true</jk> if this builder is configured to emit the report-only header variant.
519       *
520       * @return A boolean flag.
521       */
522      public boolean isReportOnly() {
523         return reportOnly;
524      }
525
526      /**
527       * Builds the rendered policy value (the header value only, not including the header name).
528       *
529       * <p>
530       * Returns an empty string when no directives have been registered.
531       *
532       * @return The policy value. Never <jk>null</jk>.
533       */
534      public String build() {
535         var parts = new ArrayList<String>(directives.size());
536         directives.forEach((name, sources) -> {
537            if (sources.isEmpty())
538               parts.add(name);
539            else
540               parts.add(name + " " + String.join(" ", sources));
541         });
542         return String.join("; ", parts);
543      }
544
545      /**
546       * Builds a {@link ContentSecurityPolicy} header bean from this builder.
547       *
548       * <p>
549       * The header name is {@link ContentSecurityPolicy#REPORT_ONLY_NAME} when {@link #isReportOnly()} is <jk>true</jk>,
550       * otherwise {@link ContentSecurityPolicy#NAME}.
551       *
552       * @return A new header bean carrying {@link #build()}. Never <jk>null</jk>.
553       */
554      public ContentSecurityPolicy toHeader() {
555         return reportOnly ? ContentSecurityPolicy.ofReportOnly(build()) : ContentSecurityPolicy.of(build());
556      }
557
558      @Override
559      public String toString() {
560         return build();
561      }
562   }
563}