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:
@@ -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.
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user