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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
# SPDX-License-Identifier: CC0-1.0
"""
Haketilo unit tests - determining what to do on a given web page
"""
# This file is part of Haketilo
#
# Copyright (C) 2021, Wojtek Kosior <koszko@koszko.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the CC0 1.0 Universal License as published by
# the Creative Commons Corporation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# CC0 1.0 Universal License for more details.
import re
from hashlib import sha256
import pytest
from ..script_loader import load_script
csp_re = re.compile(r'''
^
\S+(?:\s+\S+)+; # first directive
(?:
\s+\S+(?:\s+\S+)+; # subsequent directive
)*
$
''',
re.VERBOSE)
rule_re = re.compile(r'''
^
\s*
(?P<src_kind>\S+)
\s+
(?P<allowed_origins>
\S+(?:\s+\S+)*
)
$
''', re.VERBOSE)
def parse_csp(csp):
'''Parsing of CSP string into a dict.'''
assert csp_re.match(csp)
result = {}
for rule in csp.split(';')[:-1]:
match = rule_re.match(rule)
result[match.group('src_kind')] = match.group('allowed_origins').split()
return result
@pytest.mark.get_page('https://gotmyowndoma.in')
def test_decide_policy(execute_in_page):
"""
policy.js contains code that, using a Pattern Query Tree instance and a URL,
decides what Haketilo should do on a page opened at that URL, i.e. whether
it should block or allow script execution and whether it should inject its
own scripts and which ones. Test that the policy object gets constructed
properly.
"""
execute_in_page(load_script('common/policy.js'))
policy = execute_in_page(
'''
returnval(decide_policy(pqt.make(), "http://unkno.wn/", true, "abcd"));
''')
assert policy['allow'] == True
for prop in ('mapping', 'payload', 'nonce', 'csp', 'error'):
assert prop not in policy
policy = execute_in_page(
'''{
const tree = pqt.make();
pqt.register(tree, "http://kno.wn", "~allow", 1);
returnval(decide_policy(tree, "http://kno.wn/", false, "abcd"));
}''')
assert policy['allow'] == True
assert policy['mapping'] == '~allow'
for prop in ('payload', 'nonce', 'csp', 'error'):
assert prop not in policy
policy = execute_in_page(
'''
returnval(decide_policy(pqt.make(), "http://unkno.wn/", false, "abcd"));
'''
)
assert policy['allow'] == False
for prop in ('mapping', 'payload', 'nonce', 'error'):
assert prop not in policy
assert parse_csp(policy['csp']) == {
'prefetch-src': ["'none'"],
'script-src-attr': ["'none'"],
'script-src': ["'none'", "'unsafe-eval'"],
'script-src-elem': ["'none'"]
}
policy = execute_in_page(
'''{
const tree = pqt.make();
pqt.register(tree, "http://kno.wn", "~allow", 0);
returnval(decide_policy(tree, "http://kno.wn/", true, "abcd"));
}''')
assert policy['allow'] == False
assert policy['mapping'] == '~allow'
for prop in ('payload', 'nonce', 'error'):
assert prop not in policy
assert parse_csp(policy['csp']) == {
'prefetch-src': ["'none'"],
'script-src-attr': ["'none'"],
'script-src': ["'none'", "'unsafe-eval'"],
'script-src-elem': ["'none'"]
}
policy = execute_in_page(
'''{
const tree = pqt.make();
pqt.register(tree, "http://kno.wn", "m1", {identifier: "res1"});
returnval(decide_policy(tree, "http://kno.wn/", true, "abcd"));
}''')
assert policy['allow'] == False
assert policy['mapping'] == 'm1'
assert policy['payload'] == {'identifier': 'res1'}
assert 'error' not in policy
assert policy['nonce'] == \
sha256('m1:res1:http://kno.wn/:abcd'.encode()).digest().hex()
assert parse_csp(policy['csp']) == {
'prefetch-src': ["'none'"],
'script-src-attr': ["'none'"],
'script-src': [f"'nonce-{policy['nonce']}'", "'unsafe-eval'"],
'script-src-elem': [f"'nonce-{policy['nonce']}'"]
}
policy = execute_in_page(
'returnval(decide_policy(pqt.make(), "<bad_url>", true, "abcd"));'
)
assert policy['allow'] == False
assert policy['error'] == {'haketilo_error_type': 'deciding_policy'}
for prop in ('mapping', 'payload', 'nonce'):
assert prop not in policy
assert parse_csp(policy['csp']) == {
'prefetch-src': ["'none'"],
'script-src-attr': ["'none'"],
'script-src': ["'none'", "'unsafe-eval'"],
'script-src-elem': ["'none'"]
}
|