Skip to content

Commit

Permalink
feat: remove hpkp tests (#521)
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoMcA committed Jan 25, 2024
1 parent d55d484 commit 7e76016
Show file tree
Hide file tree
Showing 12 changed files with 9 additions and 341 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ $ poetry install
path='/foo/bar', # don't scan /, instead scan /foo/bar
cookies={'foo': 'bar'}, # set the "foo" cookie to "bar"
headers={'X-Foo': 'bar'}, # send an X-Foo: bar HTTP header
verify=False) # treat self-signed certs as valid for tests like HSTS/HPKP
verify=False) # treat self-signed certs as valid for tests like HSTS
```

### The same, but with the local CLI
Expand Down
15 changes: 0 additions & 15 deletions httpobs/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,21 +394,6 @@ Example:
"score_description": "Content is not visible via cross-origin resource sharing (CORS) files or headers",
"score_modifier": 0
},
"public-key-pinning": {
"expectation": "hpkp-not-implemented",
"name": "public-key-pinning",
"output": {
"data": null,
"includeSubDomains": false,
"max-age": null,
"numPins": null,
"preloaded": false
},
"pass": true,
"result": "hpkp-not-implemented",
"score_description": "HTTP Public Key Pinning (HPKP) header not implemented",
"score_modifier": 0
},
"redirection": {
"expectation": "redirection-to-https",
"name": "redirection",
Expand Down
11 changes: 0 additions & 11 deletions httpobs/docs/scoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,6 @@ csp-not-implemented | Content Security Policy (CSP) header not implemented | -25
csp-header-invalid | Content Security Policy (CSP) header cannot be parsed successfully | -25
<br>

[HTTP Public Key Pinning](https://infosec.mozilla.org/guidelines/web_security#http-public-key-pinning) | Description | Modifier
--- | --- | :---:
hpkp-preloaded | Preloaded via the HTTP Public Key Pinning (HPKP) preloading process | 0
hpkp-implemented-<br>max-age-at-least-fifteen-days | HTTP Public Key Pinning (HPKP) header set to a minimum of 15 days (1296000) | 0
hpkp-implemented-<br>max-age-less-than-fifteen-days | HTTP Public Key Pinning (HPKP) header set to less than 15 days (1296000) | 0
hpkp-not-implemented | HTTP Public Key Pinning (HPKP) header not implemented | 0
hpkp-invalid-cert | HTTP Public Key Pinning (HPKP) header cannot be set, as site contains an invalid certificate chain | 0
hpkp-not-implemented-no-https | HTTP Public Key Pinning (HPKP) header can't be implemented without https | 0
hpkp-header-invalid | HTTP Public Key Pinning (HPKP) header cannot be recognized | -5
<br>

[HTTP Strict Transport Security](https://infosec.mozilla.org/guidelines/web_security#http-strict-transport-security) | Description | Modifier
--- | --- | :---:
hsts-preloaded | Preloaded via the HTTP Strict Transport Security (HSTS) preloading process | 5
Expand Down
2 changes: 0 additions & 2 deletions httpobs/scanner/analyzer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from .headers import (
content_security_policy,
cookies,
public_key_pinning,
referrer_policy,
strict_transport_security,
x_content_type_options,
Expand All @@ -18,7 +17,6 @@
cookies,
contribute,
cross_origin_resource_sharing,
public_key_pinning,
redirection,
referrer_policy,
strict_transport_security,
Expand Down
88 changes: 1 addition & 87 deletions httpobs/scanner/analyzer/headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from urllib.parse import urlparse, urlunparse

from httpobs.scanner.analyzer.decorators import scored_test
from httpobs.scanner.analyzer.utils import is_hpkp_preloaded, is_hsts_preloaded, only_if_worse
from httpobs.scanner.analyzer.utils import is_hsts_preloaded, only_if_worse
from httpobs.scanner.retriever import get_duplicate_header_values

# Ignore the CloudFlare __cfduid tracking cookies. They *are* actually bad, but it is out of a site's
Expand Down Expand Up @@ -488,92 +488,6 @@ def cookies(reqs: dict, expectation='cookies-secure-with-httponly-sessions') ->
return output


@scored_test
def public_key_pinning(reqs: dict, expectation='hpkp-not-implemented') -> dict:
"""
:param reqs: dictionary containing all the request and response objects
:param expectation: test expectation; possible results:
hpkp-not-implemented-no-https
hpkp-not-implemented
hpkp-implemented-max-age-less-than-fifteen-days
hpkp-implemented-max-age-at-least-fifteen-days
hpkp-preloaded
hpkp-header-invalid
hpkp-invalid-cert
:return: dictionary with:
data: the raw HPKP header
includesubdomains: whether the includeSubDomains directive is set
max-age: what the max
num-pins: the number of pins
expectation: test expectation
pass: whether the site's configuration met its expectation
result: short string describing the result of the test
"""
FIFTEEN_DAYS = 1296000

output = {
'data': None,
'expectation': expectation,
'includeSubDomains': False,
'max-age': None,
'numPins': None,
'pass': True,
'preloaded': False,
'result': 'hpkp-not-implemented',
}
response = reqs['responses']['https']

# If there's no HTTPS, we can't have HPKP
if response is None:
output['result'] = 'hpkp-not-implemented-no-https'

# Can't have HPKP without a valid certificate chain
elif not response.verified:
output['result'] = 'hpkp-invalid-cert'

elif 'Public-Key-Pins' in response.headers:
output['data'] = response.headers['Public-Key-Pins'][0:2048] # code against malicious headers

try:
pkp = [i.lower().strip() for i in output['data'].split(';')]
pins = []

for parameter in pkp:
if parameter.startswith('max-age='):
output['max-age'] = int(parameter[8:128]) # defense
elif parameter.startswith('pin-sha256=') and parameter not in pins:
pins.append(parameter)
elif parameter == 'includesubdomains':
output['includeSubDomains'] = True
output['numPins'] = len(pins)

# You must set a max-age with HPKP
if output['max-age']:
if output['max-age'] < FIFTEEN_DAYS:
output['result'] = 'hpkp-implemented-max-age-less-than-fifteen-days'
else:
output['result'] = 'hpkp-implemented-max-age-at-least-fifteen-days'

# You must have at least two pins with HPKP and set max-age
if not output['max-age'] or len(pins) < 2:
raise ValueError

except:
output['result'] = 'hpkp-header-invalid'
output['pass'] = False

# If they're in the preloaded list, this overrides most anything else
if response is not None:
preloaded = is_hpkp_preloaded(urlparse(response.url).netloc)
if preloaded:
output['result'] = 'hpkp-preloaded'
output['includeSubDomains'] = preloaded['includeSubDomainsForPinning']
output['preloaded'] = True

# No need to check pass/fail here, the only way to fail is to have an invalid header
return output


@scored_test
def referrer_policy(reqs: dict, expectation='referrer-policy-private') -> dict:
"""
Expand Down
19 changes: 0 additions & 19 deletions httpobs/scanner/analyzer/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,6 @@
hsts = json.load(f)


def is_hpkp_preloaded(hostname):
# Just see if the hostname is in the HSTS list and pinned
if hsts.get(hostname, {}).get('pinned'):
return hsts[hostname]

# Either the hostname is in the list *or* one of its subdomains is
host = hostname.split('.')
levels = len(host)

# If hostname is foo.bar.baz.mozilla.org, check bar.baz.mozilla.org, baz.mozilla.org, mozilla.org, and .org
for i in range(1, levels):
domain = '.'.join(host[i:levels])

if hsts.get(domain, {}).get('pinned') is True and hsts.get(domain, {}).get('includeSubDomainsForPinning'):
return hsts[domain]

return False


def is_hsts_preloaded(hostname):
# Just see if the hostname is the HSTS list with the right mode -- no need to check includeSubDomains
if hsts.get(hostname, {}).get('mode') == 'force-https':
Expand Down
31 changes: 0 additions & 31 deletions httpobs/scanner/grader/grade.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,37 +171,6 @@
'description': 'Content is visible via cross-origin resource sharing (CORS) file or headers',
'modifier': -50,
},
# Public Key Pinning
'hpkp-preloaded': {
'description': 'Preloaded via the HTTP Public Key Pinning (HPKP) preloading process',
'modifier': 0,
},
'hpkp-implemented-max-age-at-least-fifteen-days': {
'description': 'HTTP Public Key Pinning (HPKP) header set to a minimum of 15 days (1296000)',
'modifier': 0,
},
'hpkp-implemented-max-age-less-than-fifteen-days': {
'description': 'HTTP Public Key Pinning (HPKP) header set to less than 15 days (1296000)',
'modifier': 0,
},
'hpkp-not-implemented': {
'description': 'HTTP Public Key Pinning (HPKP) header not implemented',
'modifier': 0,
},
'hpkp-not-implemented-no-https': {
'description': 'HTTP Public Key Pinning (HPKP) header can\'t be implemented without HTTPS',
'modifier': 0,
},
'hpkp-invalid-cert': {
'description': (
'HTTP Public Key Pinning (HPKP) header cannot be set, ' 'as site contains an invalid certificate chain'
),
'modifier': 0,
},
'hpkp-header-invalid': {
'description': 'HTTP Public Key Pinning (HPKP) header cannot be recognized',
'modifier': -5,
},
# Redirection
'redirection-all-redirects-preloaded': {
'description': 'All hosts redirected to are in the HTTP Strict Transport Security (HSTS) preload list',
Expand Down
2 changes: 1 addition & 1 deletion httpobs/scanner/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def scan(hostname: str, **kwargs):
path (str): path to scan, instead of "/"
verify (bool): whether to enable or disable certificate verification,
enabled by default. This can allow tested sites to pass the HSTS
and HPKP tests, even with self-signed certificates.
tests, even with self-signed certificates.
cookies (dict): Cookies sent to the system being scanned. Matches the
requests cookie dict.
Expand Down
4 changes: 1 addition & 3 deletions httpobs/scripts/scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ def main():
parser.add_argument('--http-port', default=80, help='port to use for the HTTP scan (instead of 80)', type=int)
parser.add_argument('--https-port', default=443, help='port to use for the HTTPS scan (instead of 443)', type=int)
parser.add_argument('--path', default=argparse.SUPPRESS, help='path to scan, instead of /', type=str)
parser.add_argument(
'--no-verify', action='store_true', help='disable certificate verification in the HSTS/HPKP tests'
)
parser.add_argument('--no-verify', action='store_true', help='disable certificate verification in the HSTS tests')
parser.add_argument(
'--cookies', default=argparse.SUPPRESS, help='cookies to send in scan (json formatted)', type=json.loads
)
Expand Down
6 changes: 3 additions & 3 deletions httpobs/tests/unittests/test_grades.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
class TestGrader(TestCase):
def test_get_score_description(self):
self.assertEquals(
'Preloaded via the HTTP Public Key Pinning (HPKP) preloading process',
get_score_description('hpkp-preloaded'),
'Contribute.json implemented with the required contact information',
get_score_description('contribute-json-with-required-keys'),
)

def test_get_score_modifier(self):
self.assertEquals(0, get_score_modifier('hpkp-preloaded'))
self.assertEquals(0, get_score_modifier('contribute-json-with-required-keys'))

0 comments on commit 7e76016

Please sign in to comment.