kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
Fix #328: Validate Date header in HTTP Signatures
rodzic
2a7333df6f
commit
9017acdb39
|
@ -5,6 +5,7 @@ import requests
|
||||||
import requests_http_signature
|
import requests_http_signature
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.http import http_date
|
||||||
|
|
||||||
from funkwhale_api.factories import registry
|
from funkwhale_api.factories import registry
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ class SignedRequestFactory(factory.Factory):
|
||||||
default_headers = {
|
default_headers = {
|
||||||
"User-Agent": "Test",
|
"User-Agent": "Test",
|
||||||
"Host": "test.host",
|
"Host": "test.host",
|
||||||
"Date": "Right now",
|
"Date": http_date(timezone.now().timestamp()),
|
||||||
"Content-Type": "application/activity+json",
|
"Content-Type": "application/activity+json",
|
||||||
}
|
}
|
||||||
if extracted:
|
if extracted:
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.http import parse_http_date
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import requests_http_signature
|
import requests_http_signature
|
||||||
|
@ -7,8 +13,33 @@ from . import exceptions, utils
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# the request Date should be between now - 30s and now + 30s
|
||||||
|
DATE_HEADER_VALID_FOR = 30
|
||||||
|
|
||||||
|
|
||||||
|
def verify_date(raw_date):
|
||||||
|
if not raw_date:
|
||||||
|
raise forms.ValidationError("Missing date header")
|
||||||
|
|
||||||
|
try:
|
||||||
|
ts = parse_http_date(raw_date)
|
||||||
|
except ValueError as e:
|
||||||
|
raise forms.ValidationError(str(e))
|
||||||
|
dt = datetime.datetime.utcfromtimestamp(ts)
|
||||||
|
dt = dt.replace(tzinfo=pytz.utc)
|
||||||
|
delta = datetime.timedelta(seconds=DATE_HEADER_VALID_FOR)
|
||||||
|
now = timezone.now()
|
||||||
|
if dt < now - delta or dt > now + delta:
|
||||||
|
raise forms.ValidationError(
|
||||||
|
"Request Date is too far in the future or in the past"
|
||||||
|
)
|
||||||
|
|
||||||
|
return dt
|
||||||
|
|
||||||
|
|
||||||
def verify(request, public_key):
|
def verify(request, public_key):
|
||||||
|
verify_date(request.headers.get("Date"))
|
||||||
|
|
||||||
return requests_http_signature.HTTPSignatureAuth.verify(
|
return requests_http_signature.HTTPSignatureAuth.verify(
|
||||||
request, key_resolver=lambda **kwargs: public_key, use_auth_header=False
|
request, key_resolver=lambda **kwargs: public_key, use_auth_header=False
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
import cryptography.exceptions
|
import cryptography.exceptions
|
||||||
|
import datetime
|
||||||
|
from django.utils.http import http_date
|
||||||
|
from django import forms
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from funkwhale_api.federation import keys, signing
|
from funkwhale_api.federation import keys, signing
|
||||||
|
@ -36,6 +39,20 @@ def test_verify_fails_with_wrong_key(nodb_factories):
|
||||||
signing.verify(prepared_request, wrong_public)
|
signing.verify(prepared_request, wrong_public)
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_fails_with_wrong_date(nodb_factories, now):
|
||||||
|
too_old = now - datetime.timedelta(seconds=31)
|
||||||
|
too_old = http_date(too_old.timestamp())
|
||||||
|
private, public = nodb_factories["federation.KeyPair"]()
|
||||||
|
auth = nodb_factories["federation.SignatureAuth"](key=private)
|
||||||
|
request = nodb_factories["federation.SignedRequest"](
|
||||||
|
auth=auth, headers={"Date": too_old}
|
||||||
|
)
|
||||||
|
prepared_request = request.prepare()
|
||||||
|
|
||||||
|
with pytest.raises(forms.ValidationError):
|
||||||
|
signing.verify(prepared_request, public)
|
||||||
|
|
||||||
|
|
||||||
def test_can_verify_django_request(factories, fake_request):
|
def test_can_verify_django_request(factories, fake_request):
|
||||||
private_key, public_key = keys.get_key_pair()
|
private_key, public_key = keys.get_key_pair()
|
||||||
signed_request = factories["federation.SignedRequest"](
|
signed_request = factories["federation.SignedRequest"](
|
||||||
|
@ -95,14 +112,18 @@ def test_can_verify_django_request_digest_failure(factories, fake_request):
|
||||||
signing.verify_django(django_request, public_key)
|
signing.verify_django(django_request, public_key)
|
||||||
|
|
||||||
|
|
||||||
def test_can_verify_django_request_failure(factories, fake_request):
|
def test_can_verify_django_request_failure(factories, fake_request, now):
|
||||||
private_key, public_key = keys.get_key_pair()
|
private_key, public_key = keys.get_key_pair()
|
||||||
signed_request = factories["federation.SignedRequest"](
|
signed_request = factories["federation.SignedRequest"](
|
||||||
auth__key=private_key, auth__headers=["date"]
|
auth__key=private_key, auth__headers=["date"]
|
||||||
)
|
)
|
||||||
prepared = signed_request.prepare()
|
prepared = signed_request.prepare()
|
||||||
django_request = fake_request.get(
|
django_request = fake_request.get(
|
||||||
"/", **{"HTTP_DATE": "Wrong", "HTTP_SIGNATURE": prepared.headers["signature"]}
|
"/",
|
||||||
|
**{
|
||||||
|
"HTTP_DATE": http_date((now + datetime.timedelta(seconds=31)).timestamp()),
|
||||||
|
"HTTP_SIGNATURE": prepared.headers["signature"],
|
||||||
|
}
|
||||||
)
|
)
|
||||||
with pytest.raises(cryptography.exceptions.InvalidSignature):
|
with pytest.raises(forms.ValidationError):
|
||||||
signing.verify_django(django_request, public_key)
|
signing.verify_django(django_request, public_key)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Validate Date header in HTTP Signatures (#328)
|
Ładowanie…
Reference in New Issue