diff options
-rw-r--r-- | apkkit/io/apkfile.py | 159 |
1 files changed, 108 insertions, 51 deletions
diff --git a/apkkit/io/apkfile.py b/apkkit/io/apkfile.py index d3e1237..9e713b7 100644 --- a/apkkit/io/apkfile.py +++ b/apkkit/io/apkfile.py @@ -2,6 +2,7 @@ from apkkit.base.package import Package from apkkit.io.util import recursive_size +from getpass import getpass import glob import gzip import hashlib @@ -19,9 +20,11 @@ LOGGER = logging.getLogger(__name__) try: - import rsa + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import padding except ImportError: - LOGGER.warning("RSA module is not available - signing packages won't work.") + LOGGER.warning("cryptography module is unavailable - can't sign packages.") def _ensure_no_debug(filename): @@ -36,7 +39,7 @@ def _sign_control(control, privkey, pubkey): """Sign control.tar. :param control: - A file-like object representing the current control.tar. + A file-like object representing the current control.tar.gz. :param privkey: The path to the private key. @@ -46,12 +49,22 @@ def _sign_control(control, privkey, pubkey): signature, so it must match /etc/apk/keys/<name>). :returns: - A file-like object representing the signed control.tar. + A file-like object representing the signed control.tar.gz. """ - control.seek(0) - control_hash = hashlib.sha256(control.read()) + signature = None + + with open(privkey, "rb") as key_file: + private_key = serialization.load_pem_private_key( + key_file.read(), + password=getpass() or None, + backend=default_backend() + ) + signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) + signer.update(control.getvalue()) + signature = signer.finalize() + del signer + del private_key - signature = b'signed sha256sum here' iosignature = io.BytesIO(signature) new_control = io.BytesIO() @@ -60,12 +73,88 @@ def _sign_control(control, privkey, pubkey): tarinfo.size = len(signature) new_control_tar.addfile(tarinfo, fileobj=iosignature) + new_control.seek(0) + controlgz = io.BytesIO() + with gzip.GzipFile(mode='wb', fileobj=controlgz) as gzobj: + shutil.copyfileobj(new_control, gzobj) + control.seek(0) - new_control.seek(0, 2) + controlgz.seek(0, 2) + shutil.copyfileobj(control, controlgz) + + controlgz.seek(0) + + new_control_tar.close() + new_control.close() + return controlgz + + +def _make_data_tgz(datadir, mode): + """Make the data.tar.gz file. + + :param str datadir: + The base directory for the package's data. + + :param str mode: + The mode to open the file ('x' or 'w'). + + :returns: + A file-like object representing the data.tar.gz file. + """ + fd, pkg_data_path = mkstemp(prefix='apkkit-', suffix='.tar') + gzio = io.BytesIO() + + with os.fdopen(fd, 'xb') as fdfile: + with tarfile.open(mode=mode, fileobj=fdfile, + format=tarfile.PAX_FORMAT) as data: + for item in glob.glob(datadir + '/*'): + data.add(item, arcname=os.path.basename(item), + exclude=_ensure_no_debug) + + LOGGER.info('Hashing data.tar [pass 1]...') + fdfile.seek(0) + abuild_pipe = Popen(['abuild-tar', '--hash'], stdin=fdfile, + stdout=PIPE) + + LOGGER.info('Compressing data...') + with gzip.GzipFile(mode='wb', fileobj=gzio) as gzobj: + gzobj.write(abuild_pipe.communicate()[0]) + + return gzio - shutil.copyfileobj(control, new_control) - new_control.seek(0) - return new_control + +def _make_control_tgz(package, mode): + """Make the control.tar.gz file. + + :param package: + The :py:class:`~apkkit.base.package.Package` instance for the package. + + :param str mode: + The mode to use for control.tar ('x' or 'w'). + + :returns: + A file-like object representing the control.tar.gz file. + """ + gzio = io.BytesIO() + control = io.BytesIO() + + control_tar = tarfile.open(mode=mode, fileobj=control) + + ioinfo = io.BytesIO(package.to_pkginfo().encode('utf-8')) + tarinfo = tarfile.TarInfo('.PKGINFO') + ioinfo.seek(0, 2) + tarinfo.size = ioinfo.tell() + ioinfo.seek(0) + + control_tar.addfile(tarinfo, fileobj=ioinfo) + + control.seek(0) + with gzip.GzipFile(mode='wb', fileobj=gzio) as control_obj: + shutil.copyfileobj(control, control_obj) + + control_tar.close() + + return gzio class APKFile: @@ -122,48 +211,23 @@ class APKFile: else: mode = 'w' - fd, pkg_data_path = mkstemp(prefix='apkkit-', suffix='.tar') - gzio = io.BytesIO() - LOGGER.info('Creating data.tar...') - with os.fdopen(fd, 'xb') as fdfile: - with tarfile.open(mode=mode, fileobj=fdfile, - format=tarfile.PAX_FORMAT) as data: - for item in glob.glob(datadir + '/*'): - data.add(item, arcname=os.path.basename(item), - exclude=_ensure_no_debug) - - LOGGER.info('Hashing data.tar [pass 1]...') - fdfile.seek(0) - abuild_pipe = Popen(['abuild-tar', '--hash'], stdin=fdfile, - stdout=PIPE) - - LOGGER.info('Compressing data...') - with gzip.GzipFile(mode='wb', fileobj=gzio) as gzobj: - gzobj.write(abuild_pipe.communicate()[0]) + data_gzio = _make_data_tgz(datadir, mode) # make the datahash if data_hash: LOGGER.info('Hashing data.tar [pass 2]...') - gzio.seek(0) - hasher = getattr(hashlib, hash_method)(gzio.read()) + data_gzio.seek(0) + hasher = getattr(hashlib, hash_method)(data_gzio.read()) package.data_hash = hasher.hexdigest() # if we made the hash, we need to seek back again # if we didn't, we haven't seeked back yet - gzio.seek(0) + data_gzio.seek(0) # we are finished with fdfile (data.tar), now let's make control LOGGER.info('Creating package header...') - - control = io.BytesIO() - control_tar = tarfile.open(mode=mode, fileobj=control) - ioinfo = io.BytesIO(package.to_pkginfo().encode('utf-8')) - tarinfo = tarfile.TarInfo('.PKGINFO') - ioinfo.seek(0, 2) - tarinfo.size = ioinfo.tell() - ioinfo.seek(0) - control_tar.addfile(tarinfo, fileobj=ioinfo) + controlgz = _make_control_tgz(package, mode) # we do NOT close control_tar yet, because we don't want the end of # archive written out. @@ -172,22 +236,15 @@ class APKFile: signfile = os.getenv('PACKAGE_PRIVKEY', signfile) pubkey = os.getenv('PACKAGE_PUBKEY', os.path.basename(signfile) + '.pub') - control = _sign_control(control, signfile, pubkey) - - LOGGER.info('Compressing package header...') - controlgz = io.BytesIO() - with gzip.GzipFile(mode='wb', fileobj=controlgz) as gzobj: - shutil.copyfileobj(control, gzobj) - control_tar.close() # we are done with it now - controlgz.seek(0) + controlgz = _sign_control(controlgz, signfile, pubkey) LOGGER.info('Creating package file (in memory)...') combined = io.BytesIO() shutil.copyfileobj(controlgz, combined) - shutil.copyfileobj(gzio, combined) + shutil.copyfileobj(data_gzio, combined) controlgz.close() - gzio.close() + data_gzio.close() return cls(fileobj=combined, package=package) |