scripts: Various improvements in pisign.py

- Get passphrase with getpass
- Don't extract public key from certificate
- Support signing/verifying multiple files at once
This commit is contained in:
Bahadır Kandemir
2010-10-25 09:15:57 +00:00
parent 65ca49778e
commit 66f8c6cb52
2 changed files with 152 additions and 86 deletions
+9 -6
View File
@@ -5,8 +5,9 @@ mkdir certs
openssl genrsa -des3 -out certs/enc_key.pem 1024 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 -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 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: # Create a test file:
@@ -17,13 +18,15 @@ echo "ABC" > dummy/a/b/test.txt
echo "^+%" > dummy/a/b/c/test.txt echo "^+%" > dummy/a/b/c/test.txt
zip -r test.zip dummy zip -r test.zip dummy
rm -rf 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: <Enter 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.
+143 -80
View File
@@ -1,158 +1,221 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
PiSi Package Signing Tools
"""
import base64 import base64
import getpass
import os
import hashlib import hashlib
import shlex
import subprocess import subprocess
import sys import sys
import tempfile
import zipfile import zipfile
def sign_data(data, certificate, password_fd):
def signData(data, keyfile, passphrase):
""" """
Signs data with given key file and passphrase. Signs data with given certificate.
Arguments: Arguments:
data: Data to sign data: Data to be signed
keyfile: Private key certificate: Private certificate
passphrase: Passphrase password_fd: File that contains passphrase
Returns: Returns:
Signed data 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) # Use OpenSSL to sign data
pipe = subprocess.Popen(cmd.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 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.write(data)
pipe.stdin.close() 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: Arguments:
data: Original data data: Original data
signature: Signed data signature_file: Signed data
keyfile: Public keyfile key_file: Public keyfile
certificate: Certificate
Returns: Returns:
True if valid, False if invalid 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: # Keep data in a temporary file
cmd = '/usr/bin/openssl x509 -inform pem -in %s -pubkey -noout' % certificate data_file = tempfile.NamedTemporaryFile()
pipe = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) data_file.write(data)
keyfile = '.tmp_key' data_file.flush()
# 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 # Use OpenSSL to verify signature
file('.tmp_data', 'w').write(data) command = '/usr/bin/openssl dgst -sha1 -verify %s -signature %s %s'
file('.tmp_signature', 'w').write(signature) 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(command, stdout=subprocess.PIPE,
pipe = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) stderr=subprocess.PIPE)
return pipe.wait() == 0 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: Arguments:
zip: ZipFile object zip_obj: ZipFile object
Returns: Returns:
File names and sums ZIP content hashes
""" """
hashes = []
data = [] for info in zip_obj.infolist():
for content in zip.infolist(): content = zip_obj.read(info.filename)
content_sum = hashlib.sha1(zip.read(content.filename)).hexdigest() content_hash = hashlib.sha1(content).hexdigest()
data.append('%s %s' % (content.filename, content_sum)) hashes.append('%s %s' % (info.filename, content_hash))
return '\n'.join(data)
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: Arguments:
filename: ZIP filename filename: ZIP filename
keyfile: Public keyfile key_file: Public keyfile
certificate: Certificate
Returns: Returns:
True if valid, False if invalid True if valid, False if invalid
""" """
try: try:
zip = zipfile.ZipFile(filename) zip_obj = zipfile.ZipFile(filename)
except IOError: except (IOError, zipfile.BadZipfile):
return False 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: Arguments:
filename: ZIP filename filename: File name to be signed
keyfile: Private key certificate: Private certificate
passphrase: Passphrase 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 # Mark file as modified and save it
zip._didModify = True zip_obj._didModify = True
zip.close() zip_obj.close()
def print_usage():
def printUsage():
""" """
Prints usage information of application and exits. Prints usage information of application and exits.
""" """
print 'Usage:' print "Usage:"
print ' %s sign <path/to/zipfile> <path/to/private_key> <passphrase>' % sys.argv[0] print " %s sign <path/to/private_key> <file.zip ...>" % sys.argv[0]
print ' %s verify <path/to/zipfile> <path/to/certificate>' % sys.argv[0] print " %s verify <path/to/public_key> <file.zip ...>" % sys.argv[0]
sys.exit(1) sys.exit(1)
def main():
if __name__ == '__main__': """
Main
"""
try: try:
operation, filename = sys.argv[1:3] operation = sys.argv[1]
except ValueError: except IndexError:
printUsage() print_usage()
if operation == 'sign': if operation == 'sign':
try: try:
keyfile, passphrase = sys.argv[3:5] key_file = sys.argv[2]
except ValueError: except IndexError:
printUsage() 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': elif operation == 'verify':
try: try:
certificate = sys.argv[3] key_file = sys.argv[2]
except ValueError: except IndexError:
printUsage() print_usage()
if verifyFile(filename, certificate=certificate): if len(sys.argv[3:]):
print 'File is OK' for filename in sys.argv[3:]:
if verify_zipfile(filename, key_file):
print "%s is valid." % filename
else:
print "%s is corrupted." % filename
else: else:
print 'File is corrupt' print_usage()
else: else:
printUsage() print_usage()
if __name__ == "__main__":
sys.exit(main())