Completed
Push — master ( 1a808a...4ac66b )
by
unknown
01:37
created

CUP2Middleware.__init__()   A

Complexity

Conditions 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
dl 0
loc 5
rs 9.4285
c 1
b 0
f 0
1
import logging
2
from hashlib import sha256
3
4
from django.conf import settings
5
from django.http import HttpResponse
6
from django.utils import timezone
7
8
import pytz
9
from ecdsa import SigningKey
10
from ecdsa.util import sigencode_der
11
12
logger = logging.getLogger(__name__)
13
14
15
class CUP2Exception(Exception):
16
    pass
17
18
19
class TimezoneMiddleware(object):
20
    def process_request(self, request):
21
        tzname = request.session.get('django_timezone')
22
        if tzname:
23
            timezone.activate(pytz.timezone(tzname))
24
        else:
25
            timezone.deactivate()
26
27
28
class CUP2Middleware(object):
29
    """Support CUP2 protocol of Omaha Client.
30
    """
31
32
    def __init__(self):
33
        self.sk = {}
34
        # Loading signature keys to memory
35
        for keyid, private_key in settings.CUP_PEM_KEYS.iteritems():
36
            self.sk[keyid] = SigningKey.from_pem(open(private_key).read())
37
38
    def process_request(self, request):
39
        if getattr(settings, 'CUP_REQUEST_VALIDATION', False) and self.is_cup2_request(request):
40
            try:
41
                self.validate_cup2_request(request)
42
            except Exception as e:
43
                logger.error('%s: %s\nrequest:\n%s\n\n%s\n' % (e.__class__.__name__, e.message,
44
                                                               request.META, request.body))
45
                msg = b'<?xml version="1.0" encoding="utf-8"?><data><message>Bad Request</message></data>'
46
                return HttpResponse(msg, status=400, content_type="text/html; charset=utf-8")
47
48
    def process_response(self, request, response):
49
        if self.is_cup2_request(request) and response.status_code // 100 == 2:
50
            self.sign_cup2_response(request, response)
51
52
        return response
53
54
    @staticmethod
55
    def is_cup2_request(request):
56
        """Detects CUP2 request by passed cup2key parameter.
57
        """
58
        return request.GET.get('cup2key') is not None
59
60
    def validate_cup2_request(self, request):
61
        cup2key = request.GET.get('cup2key')
62
        cup2hreq = request.GET.get('cup2hreq')
63
64
        keyid, k = cup2key.split(':')
65
        if keyid not in self.sk.keys():
66
            raise CUP2Exception('There is no key with id %s' % keyid)
67
68
        request_hash = sha256(request.body).hexdigest()
69
        if cup2hreq and request_hash != cup2hreq:
70
            raise CUP2Exception('Bad request hash\n"%s" != "%s"' % (request_hash, cup2hreq))
71
72
    def sign_cup2_response(self, request, response):
73
        cup2key = request.GET.get('cup2key')
74
75
        request_hash = sha256(request.body).digest()
76
        response_hash = sha256(response.content).digest()
77
78
        keyid, k = cup2key.split(':')
79
        # hash( hash(request) | hash(response) | cup2key )
80
        message = sha256(request_hash + response_hash + cup2key.encode()).digest()
81
        signature = self.sk[keyid].sign(message, hashfunc=sha256, sigencode=sigencode_der, k=int(k))
82
83
        response['ETag'] = '%s:%s' % (signature.encode('hex'), request_hash.encode('hex'))
84