diff --git a/scripts/package-signing/README b/scripts/package-signing/README index 43dd1873..07bf3f65 100644 --- a/scripts/package-signing/README +++ b/scripts/package-signing/README @@ -5,8 +5,9 @@ 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 +openssl x509 -inform pem -in certs/cert.pem -pubkey -noout > certs/pub_key.pem -# Feel free to share 'certs/cert.pem' with anyone, keep others to yourself. +# Feel free to share 'certs/cert.pem' and 'certs/pub_key.pem' with anyone, keep others to yourself. # Create a test file: @@ -17,13 +18,15 @@ echo "ABC" > dummy/a/b/test.txt echo "^+%" > dummy/a/b/c/test.txt zip -r test.zip dummy rm -rf dummy +cp test.zip test2.zip -# Sign ZIP file: +# Sign ZIP files: -./pisign.py sign test.zip certs/enc_key.pem ******** +./pisign.py sign certs/enc_key.pem test.zip test2.zip +Password: -# Verify ZIP file: +# Verify ZIP files: -./pisign.py verify test.zip certs/cert.pem +./pisign.py verify certs/cert.pem test.zip test2.zip -# Change test.zip content and try to verify again. +# Change *.zip contents and try to verify again. diff --git a/scripts/package-signing/pisign.py b/scripts/package-signing/pisign.py index 286a6a1b..f307155e 100755 --- a/scripts/package-signing/pisign.py +++ b/scripts/package-signing/pisign.py @@ -1,158 +1,221 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +""" + PiSi Package Signing Tools +""" import base64 +import getpass +import os import hashlib +import shlex import subprocess import sys +import tempfile import zipfile - -def signData(data, keyfile, passphrase): +def sign_data(data, certificate, password_fd): """ - Signs data with given key file and passphrase. + Signs data with given certificate. Arguments: - data: Data to sign - keyfile: Private key - passphrase: Passphrase + data: Data to be signed + certificate: Private certificate + password_fd: File that contains passphrase Returns: Signed data """ + # Go to begining of password file + password_fd.seek(0) - 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) + # Use OpenSSL to sign data + command = '/usr/bin/openssl dgst -sha1 -sign %s -passin fd:%d' + command = command % (certificate, password_fd.fileno()) + command = shlex.split(command) + + pipe = subprocess.Popen(command, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) pipe.stdin.write(data) pipe.stdin.close() - return pipe.stdout.read() + # Get signed data + signed_binary = pipe.stdout.read() -def verifyData(data, signature, keyfile=None, certificate=None): + # Convert to Base 64 + signed_ascii = base64.b64encode(signed_binary) + + return signed_ascii + +def verify_data(data, signature, key_file): """ - Verifies signature. Keyfile or certificate is required. + Verifies signature of data signed with given key file. Arguments: data: Original data - signature: Signed data - keyfile: Public keyfile - certificate: Certificate + signature_file: Signed data + key_file: Public keyfile Returns: True if valid, False if invalid """ + # Keep signature in a temporary file + signature_file = tempfile.NamedTemporaryFile() + signature_file.write(signature) + signature_file.flush() - 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 + # Keep data in a temporary file + data_file = tempfile.NamedTemporaryFile() + data_file.write(data) + data_file.flush() - # TODO: This is a workaround, fix ASAP - file('.tmp_data', 'w').write(data) - file('.tmp_signature', 'w').write(signature) + # Use OpenSSL to verify signature + command = '/usr/bin/openssl dgst -sha1 -verify %s -signature %s %s' + command = command % (key_file, signature_file.name, data_file.name) + command = shlex.split(command) - 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 + pipe = subprocess.Popen(command, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + result = pipe.wait() -def getZipSums(zip): + # Destroy temporary files + signature_file.close() + data_file.close() + + return result == 0 + +def get_zip_hashes(zip_obj): """ - Calculates checksums of files in ZIP object. + Calculates content hashes of a ZIP file. + + Example hash content: + dir/file1 9971487c1c7ec8afbf4617460244ce4b9e11a867 + dir/file2 0b3e42702ef1c5190590534d0b3ee6c7fb45b0c6 + file3 7053bd69a3ba35cbcd2a635a090ec5f2cd439e29 Arguments: - zip: ZipFile object + zip_obj: ZipFile object Returns: - File names and sums + ZIP content hashes """ + hashes = [] - 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) + for info in zip_obj.infolist(): + content = zip_obj.read(info.filename) + content_hash = hashlib.sha1(content).hexdigest() + hashes.append('%s %s' % (info.filename, content_hash)) + return "\n".join(hashes) -def verifyFile(filename, keyfile=None, certificate=None): +def verify_zipfile(filename, key_file): """ - Verifies integrity of a ZIP file. Keyfile or certificate is required. + Verifies integrity of a ZIP file. Arguments: filename: ZIP filename - keyfile: Public keyfile - certificate: Certificate + key_file: Public keyfile Returns: True if valid, False if invalid """ try: - zip = zipfile.ZipFile(filename) - except IOError: + zip_obj = zipfile.ZipFile(filename) + except (IOError, zipfile.BadZipfile): return False - sums = getZipSums(zip) - signature = base64.b64decode(zip.comment) - return verifyData(sums, signature, keyfile, certificate) + # Get ZIP hashes + hashes = get_zip_hashes(zip_obj) -def signFile(filename, keyfile, passphrase): + # Read signed hash data from ZIP comment + signature = base64.b64decode(zip_obj.comment) + + # Close ZIP file + zip_obj.close() + + # Verify signed data + return verify_data(hashes, signature, key_file) + +def sign_zipfile(filename, certificate, password_fd): """ - Signs a ZIP file. + Signs ZIP file with given certificate. Arguments: - filename: ZIP filename - keyfile: Private key - passphrase: Passphrase + filename: File name to be signed + certificate: Private certificate + password_fd: File that contains passphrase """ + zip_obj = zipfile.ZipFile(filename, 'a') + + # Get ZIP hashes and sign them + hashes = get_zip_hashes(zip_obj) + hashes_signed = sign_data(hashes, certificate, password_fd) + + # Add signed data as ZIP comment + zip_obj.comment = hashes_signed - 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() + zip_obj._didModify = True + zip_obj.close() - -def printUsage(): +def print_usage(): """ Prints usage information of application and exits. """ - print 'Usage:' - print ' %s sign ' % sys.argv[0] - print ' %s verify ' % sys.argv[0] + print "Usage:" + print " %s sign " % sys.argv[0] + print " %s verify " % sys.argv[0] sys.exit(1) - -if __name__ == '__main__': +def main(): + """ + Main + """ try: - operation, filename = sys.argv[1:3] - except ValueError: - printUsage() + operation = sys.argv[1] + except IndexError: + print_usage() if operation == 'sign': try: - keyfile, passphrase = sys.argv[3:5] - except ValueError: - printUsage() + key_file = sys.argv[2] + except IndexError: + print_usage() - signFile(filename, keyfile, passphrase) + if len(sys.argv[3:]): + # Keep password in a temporary file + password = getpass.getpass() + password_fd = os.tmpfile() + password_fd.write(password) + password_fd.flush() + + for filename in sys.argv[3:]: + sign_zipfile(filename, key_file, password_fd) + print "Signed %s with %s" % (filename, key_file) + + # Destroy temporary file + password_fd.close() + else: + print_usage() elif operation == 'verify': try: - certificate = sys.argv[3] - except ValueError: - printUsage() + key_file = sys.argv[2] + except IndexError: + print_usage() - if verifyFile(filename, certificate=certificate): - print 'File is OK' + if len(sys.argv[3:]): + for filename in sys.argv[3:]: + if verify_zipfile(filename, key_file): + print "%s is valid." % filename + else: + print "%s is corrupted." % filename else: - print 'File is corrupt' + print_usage() else: - printUsage() + print_usage() + +if __name__ == "__main__": + sys.exit(main())