summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rw-r--r--omaha_server/omaha/core.py8
-rw-r--r--omaha_server/omaha/request.xsd13
-rw-r--r--omaha_server/omaha/response.xsd2
-rw-r--r--omaha_server/omaha/tests/fixtures.py10
-rw-r--r--omaha_server/omaha/utils.py13
-rw-r--r--omaha_server/omaha_server/middlewares.py73
-rw-r--r--omaha_server/omaha_server/settings.py9
-rw-r--r--requirements/base.txt1
9 files changed, 126 insertions, 10 deletions
diff --git a/README.md b/README.md
index 4a10cec..c456371 100644
--- a/README.md
+++ b/README.md
@@ -227,6 +227,7 @@ app:
| OMAHA_URL_PREFIX | no trailing slash! | |
| SENTRY_STACKTRACE_API_KEY | Auth API token | |
| OMAHA_ONLY_HTTPS | HTTPS-only | False |
+| CUP_REQUEST_VALIDATION | | False |
- [uWSGI Options](http://uwsgi-docs.readthedocs.org/en/latest/Options.html) & [Environment variables](http://uwsgi-docs.readthedocs.org/en/latest/Configuration.html#environment-variables)
- [Sentry](https://github.com/getsentry/sentry)
@@ -261,6 +262,12 @@ $ ebs-deploy deploy -e omaha-server-dev
3. Finally, in the case if you want to redirect all HTTP traffic to HTTPS, you can add `OMAHA_ONLY_HTTPS: true` to environment variables in the *environment* section.
*Warning:* Please, don't activate the redirection of HTTP to HTTPS if you don't enable HTTPS. It will lead to that an Omaha server won't be accessible.
+#### Enable Client Update Protocol v2
+
+1. Use [Omaha eckeytool](https://github.com/google/omaha/tree/master/omaha/tools/eckeytool) to generate private.pem key and cup_ecdsa_pubkey.{KEYID}.h files.
+2. Add cup_ecdsa_pubkey.{KEYID}.h to Omaha source directory /path/to/omaha/omaha/net/, set CupEcdsaRequestImpl::kCupProductionPublicKey in /path/to/omaha/omaha/net/cup_ecdsa_request.cc to new key and build Omaha client.
+3. Add private.pem keyid and path to omaha CUP_PEM_KEYS dictionary.
+
## Links
- Presentation: [omaha-server High Fidelity, High Velocity Deployments in the Cloud](http://slides.com/andreylisin/omaha-server/#/)
diff --git a/omaha_server/omaha/core.py b/omaha_server/omaha/core.py
index d5a6870..f252501 100644
--- a/omaha_server/omaha/core.py
+++ b/omaha_server/omaha/core.py
@@ -24,7 +24,7 @@ from datetime import datetime
from lxml.builder import E
-from omaha.utils import get_sec_since_midnight
+from omaha.utils import get_sec_since_midnight, get_days_since_20070101
__all__ = [
@@ -35,10 +35,12 @@ __all__ = [
def Response(apps_list, protocol='3.0', date=None, server='prod'):
- elapsed_seconds = get_sec_since_midnight(date or datetime.utcnow())
+ date = date or datetime.utcnow()
+ elapsed_seconds = get_sec_since_midnight(date)
+ elapsed_days = get_days_since_20070101(date)
resp = E.response(
dict(protocol=protocol, server=server),
- E.daystart(elapsed_seconds=str(elapsed_seconds)),
+ E.daystart(elapsed_seconds=str(elapsed_seconds), elapsed_days=str(elapsed_days)),
)
list(map(resp.append, apps_list))
return resp
diff --git a/omaha_server/omaha/request.xsd b/omaha_server/omaha/request.xsd
index e9cdeed..5d999fb 100644
--- a/omaha_server/omaha/request.xsd
+++ b/omaha_server/omaha/request.xsd
@@ -50,6 +50,9 @@
<xs:attribute name='testsource' type='xs:NCName' use="optional"/>
<xs:attribute name='dedup' type='xs:NCName' use="optional"/>
<xs:attribute name='updaterchannel' type='xs:NCName' use="optional"/>
+ <xs:attribute name='shell_version' type='xs:string' use="optional"/>
+ <xs:attribute name='periodoverridesec' type='xs:integer' use="optional"/>
+ <xs:attribute name='dlpref' type='xs:string' use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="hw">
@@ -90,7 +93,11 @@
<xs:attribute name='experiments' use='optional' type="xs:string"/>
<xs:attribute name='iid' use='optional' type="GuidType"/>
<xs:attribute name='installage' use='optional' type='xs:integer'/>
+ <xs:attribute name='installdate' use='optional' type='xs:integer'/>
<xs:attribute name='tag' use='optional' type='xs:string'/>
+ <xs:attribute name='cohort' use='optional' type='xs:string'/>
+ <xs:attribute name='cohorthint' use='optional' type='xs:string'/>
+ <xs:attribute name='cohortname' use='optional' type='xs:string'/>
</xs:complexType>
</xs:element>
<xs:element name='updatecheck'>
@@ -108,6 +115,7 @@
<xs:attribute name="extracode1" use="optional" type="xs:integer"/>
<xs:attribute name="download_time_ms" use="optional" type="xs:integer"/>
<xs:attribute name="downloaded" use="optional" type="xs:integer"/>
+ <xs:attribute name="downloader" use="optional" type="xs:string"/>
<xs:attribute name="total" use="optional" type="xs:integer"/>
<xs:attribute name="update_check_time_ms" use="optional" type="xs:integer"/>
<xs:attribute name="install_time_ms" use="optional" type="xs:integer"/>
@@ -117,6 +125,8 @@
<xs:attribute name="time_since_download_start_ms" use="optional" type="xs:integer"/>
<xs:attribute name="nextversion" use="optional" type="xs:integer"/>
<xs:attribute name="previousversion" use="optional" type="xs:integer"/>
+ <xs:attribute name="url" use="optional" type="xs:string"/>
+ <xs:attribute name="is_bundled" use="optional" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name='ping'>
@@ -124,6 +134,9 @@
<xs:attribute name='active' use='optional' type='xs:integer' default="0"/>
<xs:attribute name='r' use='optional' type='xs:integer' default="0"/>
<xs:attribute name='a' use='optional' type='xs:integer' default="0"/>
+ <xs:attribute name='rd' use='optional' type='xs:integer' default="-2"/>
+ <xs:attribute name='ad' use='optional' type='xs:integer' default="-2"/>
+ <xs:attribute name='ping_freshness' use='optional' type='xs:string' default="0"/>
</xs:complexType>
</xs:element>
<xs:element name="data" type="DataType"/>
diff --git a/omaha_server/omaha/response.xsd b/omaha_server/omaha/response.xsd
index a81cbfe..72ece40 100644
--- a/omaha_server/omaha/response.xsd
+++ b/omaha_server/omaha/response.xsd
@@ -18,7 +18,7 @@
<xs:element name="daystart">
<xs:complexType>
<xs:attribute name="elapsed_seconds" use="required" type="xs:positiveInteger"/>
- <xs:attribute name="elapsed_days" use="optional" type="xs:nonPositiveInteger" default="0"/>
+ <xs:attribute name="elapsed_days" use="required" type="xs:positiveInteger"/>
</xs:complexType>
</xs:element>
<xs:element name='app'>
diff --git a/omaha_server/omaha/tests/fixtures.py b/omaha_server/omaha/tests/fixtures.py
index 4be086e..4efdfe5 100644
--- a/omaha_server/omaha/tests/fixtures.py
+++ b/omaha_server/omaha/tests/fixtures.py
@@ -97,7 +97,7 @@ request_data = b"""<?xml version="1.0" encoding="UTF-8"?>
response_update_check_negative = b"""<?xml version="1.0" encoding="UTF-8"?>
<response protocol="3.0" server="prod">
- <daystart elapsed_seconds="56508"/>
+ <daystart elapsed_seconds="56508" elapsed_days="2557"/>
<app appid="{430FD4D0-B729-4F61-AA34-91526481799D}" status="ok">
<updatecheck status="noupdate"/>
<ping status="ok"/>
@@ -110,7 +110,7 @@ response_update_check_negative = b"""<?xml version="1.0" encoding="UTF-8"?>
response_update_check_positive = b"""<?xml version="1.0" encoding="UTF-8"?>
<response protocol="3.0" server="prod">
- <daystart elapsed_seconds="56508"/>
+ <daystart elapsed_seconds="56508" elapsed_days="2557"/>
<app appid="{430FD4D0-B729-4F61-AA34-91526481799D}" status="ok">
<updatecheck status="noupdate"/>
<ping status="ok"/>
@@ -136,7 +136,7 @@ response_update_check_positive = b"""<?xml version="1.0" encoding="UTF-8"?>
response_event = b"""<?xml version="1.0" encoding="UTF-8"?>
<response protocol="3.0" server="prod">
- <daystart elapsed_seconds="56754"/>
+ <daystart elapsed_seconds="56754" elapsed_days="2557"/>
<app appid="{D0AB2EBC-931B-4013-9FEB-C9C4C2225C8C}" status="ok">
<event status="ok"/>
<event status="ok"/>
@@ -146,7 +146,7 @@ response_event = b"""<?xml version="1.0" encoding="UTF-8"?>
response_data_doc = b"""<?xml version="1.0" encoding="UTF-8"?>
<response protocol="3.0" server="prod">
- <daystart elapsed_seconds="56754"/>
+ <daystart elapsed_seconds="56754" elapsed_days="2557"/>
<app appid="{D0AB2EBC-931B-4013-9FEB-C9C4C2225C8C}" status="ok">
<data index="verboselogging" name="install" status="ok">
app-specific values here
@@ -157,7 +157,7 @@ response_data_doc = b"""<?xml version="1.0" encoding="UTF-8"?>
response_data = b"""<?xml version="1.0" encoding="UTF-8"?>
<response protocol="3.0" server="prod">
- <daystart elapsed_seconds="56754"/>
+ <daystart elapsed_seconds="56754" elapsed_days="2557"/>
<app status="ok" appid="{430FD4D0-B729-4F61-AA34-91526481799D}">
<data status="ok" index="verboselogging" name="install">app-specific values here</data>
<data status="ok" name="untrusted"/>
diff --git a/omaha_server/omaha/utils.py b/omaha_server/omaha/utils.py
index 5be380d..bb164cf 100644
--- a/omaha_server/omaha/utils.py
+++ b/omaha_server/omaha/utils.py
@@ -47,6 +47,19 @@ def get_sec_since_midnight(date):
return delta.seconds
+def get_days_since_20070101(date):
+ """
+ Return days since 2007-01-01
+
+ >>> from datetime import datetime
+ >>> get_days_since_20070101(datetime(year=2016, month=3, day=4))
+ 3350
+ """
+ date_20070101 = datetime.datetime(year=2007, month=1, day=1, tzinfo=date.tzinfo)
+ delta = date - date_20070101
+ return delta.days
+
+
def get_id(uuid):
"""
>>> get_id('{8C65E04C-0383-4AE2-893F-4EC7C58F70DC}')
diff --git a/omaha_server/omaha_server/middlewares.py b/omaha_server/omaha_server/middlewares.py
index 3cda3f2..d51d08a 100644
--- a/omaha_server/omaha_server/middlewares.py
+++ b/omaha_server/omaha_server/middlewares.py
@@ -1,6 +1,19 @@
+import logging
+from hashlib import sha256
+
+from django.conf import settings
+from django.http import HttpResponse
from django.utils import timezone
import pytz
+from ecdsa import SigningKey
+from ecdsa.util import sigencode_der
+
+logger = logging.getLogger(__name__)
+
+
+class CUP2Exception(Exception):
+ pass
class TimezoneMiddleware(object):
@@ -9,4 +22,62 @@ class TimezoneMiddleware(object):
if tzname:
timezone.activate(pytz.timezone(tzname))
else:
- timezone.deactivate() \ No newline at end of file
+ timezone.deactivate()
+
+
+class CUP2Middleware(object):
+ """Support CUP2 protocol of Omaha Client.
+ """
+
+ def __init__(self):
+ self.sk = {}
+ # Loading signature keys to memory
+ for keyid, private_key in settings.CUP_PEM_KEYS.iteritems():
+ self.sk[keyid] = SigningKey.from_pem(open(private_key).read())
+
+ def process_request(self, request):
+ if getattr(settings, 'CUP_REQUEST_VALIDATION', False) and self.is_cup2_request(request):
+ try:
+ self.validate_cup2_request(request)
+ except Exception as e:
+ logger.error('%s: %s\nrequest:\n%s\n\n%s\n' % (e.__class__.__name__, e.message,
+ request.META, request.body))
+ msg = b'<?xml version="1.0" encoding="utf-8"?><data><message>Bad Request</message></data>'
+ return HttpResponse(msg, status=400, content_type="text/html; charset=utf-8")
+
+ def process_response(self, request, response):
+ if self.is_cup2_request(request) and response.status_code // 100 == 2:
+ self.sign_cup2_response(request, response)
+
+ return response
+
+ @staticmethod
+ def is_cup2_request(request):
+ """Detects CUP2 request by passed cup2key parameter.
+ """
+ return request.GET.get('cup2key') is not None
+
+ def validate_cup2_request(self, request):
+ cup2key = request.GET.get('cup2key')
+ cup2hreq = request.GET.get('cup2hreq')
+
+ keyid, k = cup2key.split(':')
+ if keyid not in self.sk.keys():
+ raise CUP2Exception('There is no key with id %s' % keyid)
+
+ request_hash = sha256(request.body).hexdigest()
+ if cup2hreq and request_hash != cup2hreq:
+ raise CUP2Exception('Bad request hash\n"%s" != "%s"' % (request_hash, cup2hreq))
+
+ def sign_cup2_response(self, request, response):
+ cup2key = request.GET.get('cup2key')
+
+ request_hash = sha256(request.body).digest()
+ response_hash = sha256(response.content).digest()
+
+ keyid, k = cup2key.split(':')
+ # hash( hash(request) | hash(response) | cup2key )
+ message = sha256(request_hash + response_hash + cup2key.encode()).digest()
+ signature = self.sk[keyid].sign(message, hashfunc=sha256, sigencode=sigencode_der, k=int(k))
+
+ response['ETag'] = '%s:%s' % (signature.encode('hex'), request_hash.encode('hex'))
diff --git a/omaha_server/omaha_server/settings.py b/omaha_server/omaha_server/settings.py
index ed6d9f1..2186e5d 100644
--- a/omaha_server/omaha_server/settings.py
+++ b/omaha_server/omaha_server/settings.py
@@ -135,6 +135,7 @@ if IS_PRIVATE:
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'omaha_server.middlewares.TimezoneMiddleware',
+ 'omaha_server.middlewares.CUP2Middleware',
) + MIDDLEWARE_CLASSES
ROOT_URLCONF = 'omaha_server.urls'
@@ -308,3 +309,11 @@ REST_FRAMEWORK = {
# django_select2
AUTO_RENDER_SELECT2_STATICS = False
+
+# Client Update Protocol
+
+CUP_REQUEST_VALIDATION = os.environ.get('CUP_REQUEST_VALIDATION', False)
+
+CUP_PEM_KEYS = {
+ # 'keyid': 'private_key_path',
+}
diff --git a/requirements/base.txt b/requirements/base.txt
index a99974c..795a939 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -31,6 +31,7 @@ django-bootstrap3==7.0.1
protobuf==3.0.0a3
protobuf-to-dict==0.1.0
django-dynamic-preferences==0.8.1
+ecdsa==0.13
# Only dev
#django-httplog==0.2.3