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 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: <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
# -*- 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 <path/to/zipfile> <path/to/private_key> <passphrase>' % sys.argv[0]
print ' %s verify <path/to/zipfile> <path/to/certificate>' % sys.argv[0]
print "Usage:"
print " %s sign <path/to/private_key> <file.zip ...>" % sys.argv[0]
print " %s verify <path/to/public_key> <file.zip ...>" % 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())