blob: bd275c608e9eb9aff1abbc0973d384793145b5c3 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
<?php
/* Icinga Web 2 | (c) 2023 Icinga GmbH | GPLv2+ */
namespace Icinga\Util;
use Icinga\Web\Response;
use Icinga\Web\Window;
use RuntimeException;
use function ipl\Stdlib\get_php_type;
/**
* Helper to enable strict content security policy (CSP)
*
* {@see static::addHeader()} adds a strict Content-Security-Policy header with a nonce to still support dynamic CSS
* securely.
* Note that {@see static::createNonce()} must be called first.
* Use {@see static::getStyleNonce()} to access the nonce for dynamic CSS.
*
* A nonce is not created for dynamic JS,
* and it is questionable whether this will ever be supported.
*/
class Csp
{
/** @var static */
protected static $instance;
/** @var ?string */
protected $styleNonce;
/** Singleton */
private function __construct()
{
}
/**
* Add Content-Security-Policy header with a nonce for dynamic CSS
*
* Note that {@see static::createNonce()} must be called beforehand.
*
* @param Response $response
*
* @throws RuntimeException If no nonce set for CSS
*/
public static function addHeader(Response $response): void
{
$csp = static::getInstance();
if (empty($csp->styleNonce)) {
throw new RuntimeException('No nonce set for CSS');
}
$response->setHeader('Content-Security-Policy', "style-src 'self' 'nonce-$csp->styleNonce';", true);
}
/**
* Set/recreate nonce for dynamic CSS
*
* Should always be called upon initial page loads or page reloads,
* as it sets/recreates a nonce for CSS and writes it to a window-aware session.
*/
public static function createNonce(): void
{
$csp = static::getInstance();
$csp->styleNonce = base64_encode(random_bytes(16));
Window::getInstance()->getSessionNamespace('csp')->set('style_nonce', $csp->styleNonce);
}
/**
* Get nonce for dynamic CSS
*
* @return ?string
*/
public static function getStyleNonce(): ?string
{
return static::getInstance()->styleNonce;
}
/**
* Get the CSP instance
*
* @return self
*/
protected static function getInstance(): self
{
if (static::$instance === null) {
$csp = new static();
$nonce = Window::getInstance()->getSessionNamespace('csp')->get('style_nonce');
if ($nonce !== null && ! is_string($nonce)) {
throw new RuntimeException(
sprintf(
'Nonce value is expected to be string, got %s instead',
get_php_type($nonce)
)
);
}
$csp->styleNonce = $nonce;
static::$instance = $csp;
}
return static::$instance;
}
}
|