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