From ccf88e486ec08b129d181e652d9b605ea4938ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bahad=C4=B1r=20Kandemir?= Date: Sat, 20 Feb 2010 00:27:44 +0000 Subject: [PATCH] A simple library (and also a console utility) to sign/verify PISI (ZIP) packages. --- scripts/package-signing/README | 29 ++++++ scripts/package-signing/pisign.py | 158 ++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 scripts/package-signing/README create mode 100755 scripts/package-signing/pisign.py diff --git a/scripts/package-signing/README b/scripts/package-signing/README new file mode 100644 index 00000000..43dd1873 --- /dev/null +++ b/scripts/package-signing/README @@ -0,0 +1,29 @@ +# Generate encrypted private key and self signed certificate: +# (Skip this step if you already have a key and a certificate) + +mkdir certs +openssl genrsa -des3 -out certs/enc_key.pem 1024 +openssl req -new -subj '/C=TR/ST=Kocaeli/L=Gebze/CN=Pardus' -key certs/enc_key.pem -out certs/req.pem +openssl req -x509 -key certs/enc_key.pem -in certs/req.pem -out certs/cert.pem -days 365 + +# Feel free to share 'certs/cert.pem' with anyone, keep others to yourself. + +# Create a test file: + +mkdir -p dummy/a/b/c +echo "abc" > dummy/test.txt +echo "123" > dummy/a/test.txt +echo "ABC" > dummy/a/b/test.txt +echo "^+%" > dummy/a/b/c/test.txt +zip -r test.zip dummy +rm -rf dummy + +# Sign ZIP file: + +./pisign.py sign test.zip certs/enc_key.pem ******** + +# Verify ZIP file: + +./pisign.py verify test.zip certs/cert.pem + +# Change test.zip content and try to verify again. diff --git a/scripts/package-signing/pisign.py b/scripts/package-signing/pisign.py new file mode 100755 index 00000000..286a6a1b --- /dev/null +++ b/scripts/package-signing/pisign.py @@ -0,0 +1,158 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + + +import base64 +import hashlib +import subprocess +import sys +import zipfile + + +def signData(data, keyfile, passphrase): + """ + Signs data with given key file and passphrase. + + Arguments: + data: Data to sign + keyfile: Private key + passphrase: Passphrase + Returns: + Signed data + """ + + cmd = '/usr/bin/openssl dgst -sha1 -sign %s -passin pass:%s' % (keyfile, passphrase) + pipe = subprocess.Popen(cmd.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + pipe.stdin.write(data) + pipe.stdin.close() + return pipe.stdout.read() + + +def verifyData(data, signature, keyfile=None, certificate=None): + """ + Verifies signature. Keyfile or certificate is required. + + Arguments: + data: Original data + signature: Signed data + keyfile: Public keyfile + certificate: Certificate + Returns: + True if valid, False if invalid + """ + + if certificate: + cmd = '/usr/bin/openssl x509 -inform pem -in %s -pubkey -noout' % certificate + pipe = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + keyfile = '.tmp_key' + # TODO: This is a workaround, fix ASAP + file(keyfile, 'w').write(pipe.stdout.read()) + elif not keyfile: + return False + + # TODO: This is a workaround, fix ASAP + file('.tmp_data', 'w').write(data) + file('.tmp_signature', 'w').write(signature) + + cmd = '/usr/bin/openssl dgst -sha1 -verify %s -signature .tmp_signature .tmp_data' % keyfile + pipe = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return pipe.wait() == 0 + +def getZipSums(zip): + """ + Calculates checksums of files in ZIP object. + + Arguments: + zip: ZipFile object + Returns: + File names and sums + """ + + data = [] + for content in zip.infolist(): + content_sum = hashlib.sha1(zip.read(content.filename)).hexdigest() + data.append('%s %s' % (content.filename, content_sum)) + return '\n'.join(data) + + +def verifyFile(filename, keyfile=None, certificate=None): + """ + Verifies integrity of a ZIP file. Keyfile or certificate is required. + + Arguments: + filename: ZIP filename + keyfile: Public keyfile + certificate: Certificate + Returns: + True if valid, False if invalid + """ + + try: + zip = zipfile.ZipFile(filename) + except IOError: + return False + sums = getZipSums(zip) + signature = base64.b64decode(zip.comment) + return verifyData(sums, signature, keyfile, certificate) + + +def signFile(filename, keyfile, passphrase): + """ + Signs a ZIP file. + + Arguments: + filename: ZIP filename + keyfile: Private key + passphrase: Passphrase + """ + + zip = zipfile.ZipFile(filename, 'a') + # Sign file checksums + sums = getZipSums(zip) + signature = signData(sums, keyfile, passphrase) + # Write Base64 encoded signature to ZIP file as comment + zip.comment = base64.b64encode(signature) + # Mark file as modified and save it + zip._didModify = True + zip.close() + + +def printUsage(): + """ + Prints usage information of application and exits. + """ + + print 'Usage:' + print ' %s sign ' % sys.argv[0] + print ' %s verify ' % sys.argv[0] + sys.exit(1) + + +if __name__ == '__main__': + + try: + operation, filename = sys.argv[1:3] + except ValueError: + printUsage() + + if operation == 'sign': + try: + keyfile, passphrase = sys.argv[3:5] + except ValueError: + printUsage() + + signFile(filename, keyfile, passphrase) + + elif operation == 'verify': + try: + certificate = sys.argv[3] + except ValueError: + printUsage() + + if verifyFile(filename, certificate=certificate): + print 'File is OK' + else: + print 'File is corrupt' + + else: + printUsage()