[linux-signed] 04/09: debian/bin/sign.py: Implement secure downloads

debian-kernel at lists.debian.org debian-kernel at lists.debian.org
Sat Apr 16 01:04:20 UTC 2016


This is an automated email from the git hooks/post-receive script.

benh pushed a commit to branch master
in repository linux-signed.

commit 6533d86ba0cce37a7f60aa68e13e135b5f0d8634
Author: Ben Hutchings <ben at decadent.org.uk>
Date:   Sat Apr 16 00:11:05 2016 +0100

    debian/bin/sign.py: Implement secure downloads
    
    Download and validate InRelease, Packages.gz and binary packages in
    turn.
---
 debian/bin/sign.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++----
 debian/rules       |  2 +-
 debian/rules.defs  |  3 ++
 3 files changed, 93 insertions(+), 8 deletions(-)

diff --git a/debian/bin/sign.py b/debian/bin/sign.py
index 7322024..f46e245 100755
--- a/debian/bin/sign.py
+++ b/debian/bin/sign.py
@@ -4,21 +4,102 @@ import sys
 sys.path.append(sys.argv[1] + "/lib/python")
 
 import os, os.path, shutil, subprocess, tempfile
+import deb822, codecs, gzip, hashlib, io, re, struct, urllib.parse, urllib.request
 
 from debian_linux.config import ConfigCoreDump
 from debian_linux.debian import VersionLinux
 
-def get_package(name, version, arch):
+_release_data = {}
+
+def get_release_data(mirror, suite):
+    if not _release_data:
+        url = urllib.parse.urljoin(mirror, 'dists/%s/InRelease' % suite)
+        print('I: Fetching %s' % url)
+        with urllib.request.urlopen(url) as req:
+            release_raw = req.read()
+
+        # Validate against keyring.  deb822.Release seems to expect
+        # detached signatures so call gpgv directly.
+        with tempfile.NamedTemporaryFile() as release_file:
+            release_file.write(release_raw)
+            output = subprocess.check_output(
+                ['gpgv', '--status-fd', '1',
+                 '--keyring', '/usr/share/keyrings/debian-archive-keyring.gpg',
+                 '--ignore-time-conflict', release_file.name])
+            if not re.search(r'^\[GNUPG:\]\s+VALIDSIG\s', codecs.decode(output),
+                             re.MULTILINE):
+                os.write(2, output) # bytes not str!
+                raise Exception('gpgv rejected %s' % url)
+
+        release_stream = io.TextIOWrapper(io.BytesIO(release_raw), 'utf-8')
+
+        # Make a dictionary of per-file data
+        for file_data in deb822.Release(release_stream)['SHA256']:
+            _release_data[file_data['name']] = file_data
+
+    return _release_data
+
+_packages_data = {}
+
+def get_packages_data(mirror, suite, arch):
+    if arch not in _packages_data:
+        release_data = get_release_data(mirror, suite)
+
+        path = 'main/binary-%s/Packages.gz' % arch
+        file_data = release_data[path]
+        url = urllib.parse.urljoin(mirror, 'dists/%s/%s' % (suite, path))
+        print('I: Fetching %s' % url)
+        with urllib.request.urlopen(url) as req:
+            packages_raw = req.read()
+
+        # Validate against Release file
+        if len(packages_raw) != int(file_data['size']):
+            raise Exception('%s has wrong size' % url)
+        h = hashlib.sha256()
+        h.update(packages_raw)
+        if h.digest() != bytes.fromhex(file_data['sha256']):
+            raise Exception('%s has wrong checksum' % url)
+
+        packages_stream = io.TextIOWrapper(
+            io.BytesIO(gzip.decompress(packages_raw)), 'utf-8')
+
+        # Make a dictionary of per-package data
+        _packages_data[arch] = data = {}
+        for package_data in deb822.Packages.iter_paragraphs(packages_stream):
+            data[package_data['Package']] = package_data
+
+    return _packages_data[arch]
+
+def get_package(mirror, suite, name, version, arch):
     packages_dir = 'debian/localpackages/'
     package_file = '%s/%s_%s_%s.deb' % (packages_dir, name, version, arch)
     unpack_dir = '%s/%s_%s_%s' % (packages_dir, name, version, arch)
 
-    # FIXME Need a way to download for any architecture with signature
-    # verification
     os.makedirs(packages_dir, exist_ok=True)
+
     if not os.path.isfile(package_file):
-        subprocess.check_call(['debsnap', '-d', packages_dir, '-f', '--binary',
-                               '-a', arch, name, str(version)])
+        packages_data = get_packages_data(mirror, suite, arch)
+        if name not in packages_data:
+            raise Exception('package %s is not available' % name)
+        package_data = packages_data[name]
+        if package_data['Version'] != version:
+            raise Exception('package %s version %s is not available; only version %s' %
+                            (name, version, package_data['Version']))
+        url = urllib.parse.urljoin(mirror, package_data['Filename'])
+        print('I: Fetching %s' % url)
+        with urllib.request.urlopen(url) as req:
+            package = req.read()
+
+        # Validate against Packages file
+        if len(package) != int(package_data['Size']):
+            raise Exception('%s has wrong size' % url)
+        h = hashlib.sha256()
+        h.update(package)
+        if h.digest() != bytes.fromhex(package_data['SHA256']):
+            raise Exception('%s has wrong checksum' % url)
+
+        with open(package_file, 'wb') as f:
+            f.write(package)
 
     if not os.path.isdir(unpack_dir):
         # Unpack to a temporary directory before moving into place, so we
@@ -73,7 +154,7 @@ def sign_image_efi(image_name, signature_name, privkey_name, cert_name):
         raise Exception('sbsign failed')
 
 def sign(config_name, imageversion_str, modules_privkey_name, modules_cert_name,
-         image_privkey_name, image_cert_name):
+         image_privkey_name, image_cert_name, mirror_url, suite):
     config = ConfigCoreDump(fp=open(config_name, 'rb'))
     assert config['version',]['source'] == imageversion_str
     abiname = config['version',]['abiname']
@@ -100,7 +181,8 @@ def sign(config_name, imageversion_str, modules_privkey_name, modules_cert_name,
                      flavour)
                 package_name = 'linux-image-' + kernelversion
 
-                package_dir = get_package(package_name, imageversion, arch)
+                package_dir = get_package(mirror_url, suite,
+                                          package_name, imageversion_str, arch)
 
                 signature_dir = os.path.join('debian/signatures', package_name)
                 os.makedirs(signature_dir)
diff --git a/debian/rules b/debian/rules
index 683359e..e73fc09 100755
--- a/debian/rules
+++ b/debian/rules
@@ -67,6 +67,6 @@ maintainerclean: clean
 	rm -rf $(filter-out %.config %.postinst %.templates %.NEWS, $(wildcard debian/linux-*))
 
 sign:
-	$(SIGN) /usr/src/linux-support-$(KERNEL_ABINAME) "$(KERNEL_IMAGE_VERSION)" "$(KERNEL_MODULES_PRIVKEY)" "$(KERNEL_MODULES_CERT)" "$(KERNEL_IMAGE_PRIVKEY)" "$(KERNEL_IMAGE_CERT)"
+	$(SIGN) /usr/src/linux-support-$(KERNEL_ABINAME) "$(KERNEL_IMAGE_VERSION)" "$(KERNEL_MODULES_PRIVKEY)" "$(KERNEL_MODULES_CERT)" "$(KERNEL_IMAGE_PRIVKEY)" "$(KERNEL_IMAGE_CERT)" "$(MIRROR_URL)" "$(SUITE)"
 
 .PHONY: build build-arch build-indep clean binary binary-arch binary-indep binary-arch-all maintainerclean sign
diff --git a/debian/rules.defs b/debian/rules.defs
index e9ef10a..f358ff8 100644
--- a/debian/rules.defs
+++ b/debian/rules.defs
@@ -3,6 +3,9 @@ STAMPS_DIR = debian/stamps
 TEMPLATES_DIR = debian/templates
 KERNEL_ABINAME := 4.5.0-1
 KERNEL_IMAGE_VERSION := 4.5.1-1
+SUITE = unstable
+
+MIRROR_URL = http://httpredir.debian.org/debian/
 
 # For initial testing - to be replaced with HSM
 KERNEL_SIGNER := benh at debian.org

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/kernel/linux-signed.git



More information about the Kernel-svn-changes mailing list