Last active
August 29, 2015 13:58
-
-
Save winny-/10000348 to your computer and use it in GitHub Desktop.
Validating Sparkle Appcasts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
from __future__ import print_function | |
import os | |
import requests | |
from bs4 import BeautifulSoup | |
def download_url(url, destination=None): | |
if destination is None: | |
cwd = os.getcwd() | |
file_name = url.split('/')[-1] | |
destination = os.path.join(cwd, file_name) | |
r = requests.get(url, stream=True) | |
if r.status_code != 200: | |
r.raise_for_status() | |
with open(destination, 'wb') as f: | |
for chunk in r.iter_content(chunk_size=1024): | |
if chunk: | |
f.write(chunk) | |
f.flush() | |
return destination | |
def downloading(url, details=None): | |
if details is not None: | |
details = ' ({})'.format(details) | |
else: | |
details = '' | |
print('Downloading {}{}... '.format(url, details), end='') | |
try: | |
path = download_url(url) | |
print('OK') | |
return path | |
except requests.HTTPError as ex: | |
print('{} {}'.format(ex.response.status_code, ex.response.reason)) | |
def archive_appcast(appcast_url, destdir): | |
if os.path.exists(destdir): | |
if not os.path.isdir(destdir): | |
raise RuntimeError('destdir "{}" is not a directory.'.format(destdir)) | |
else: | |
os.mkdir(destdir) | |
os.chdir(destdir) | |
appcast_path = downloading(appcast_url) | |
if not appcast_path: | |
print('Appcast file failed to dowload. Cannot continue.') | |
return | |
with open(appcast_path, 'rb') as f: | |
soup = BeautifulSoup(f.read(), 'xml') | |
for item in soup.find_all('item'): | |
title = item.title.text | |
url = item.enclosure['url'] | |
downloading(url, title) | |
class Unbuffered(object): | |
def __init__(self, stream): | |
self.stream = stream | |
def write(self, data): | |
self.stream.write(data) | |
self.stream.flush() | |
def __getattr__(self, attr): | |
return getattr(self.stream, attr) | |
if __name__ == '__main__': | |
import sys | |
sys.stdout = Unbuffered(sys.stdout) | |
archive_appcast(appcast_url=sys.argv[1], destdir=sys.argv[2]) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
from __future__ import print_function | |
import os | |
from bs4 import BeautifulSoup | |
import subprocess | |
import sys | |
color = { | |
'green': '\033[32m', | |
'red': '\033[31m', | |
'default': '\033[39m', | |
} | |
def green(s): | |
return ''.join([color['green'], s, color['default']]) | |
def red(s): | |
return ''.join([color['red'], s, color['default']]) | |
def validate(dsa_pubkey, signature, zipfile): | |
p = subprocess.Popen(['validate_sparkle_signature', dsa_pubkey, signature, zipfile], | |
stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE) | |
out, err = p.communicate() | |
output = ''.join([out, err]).strip('\n') | |
if p.returncode != 0: | |
print(red(output)) | |
else: | |
print(green(output)) | |
def validate_files(appcast, dsa_pubkey): | |
appcast = os.path.abspath(appcast) | |
dsa_pubkey = os.path.abspath(dsa_pubkey) | |
os.chdir(os.path.dirname(appcast)) | |
print('Reading {}... '.format(appcast), end='') | |
with open(appcast, 'rb') as f: | |
soup = BeautifulSoup(f.read(), 'xml') | |
print(green('OK')) | |
items = soup.find_all('item') | |
for item in items: | |
title = item.title.text | |
try: | |
url = item.enclosure['url'] | |
version = item.enclosure['sparkle:version'] | |
signature = item.enclosure['sparkle:dsaSignature'] | |
except KeyError: | |
continue | |
print('{} {} ({})... '.format(title, url, version), end='') | |
local_file = os.path.abspath(os.path.basename(url)) | |
if os.path.exists(local_file): | |
validate(dsa_pubkey, signature, local_file) | |
else: | |
print(red('file not found.')) | |
class Unbuffered(object): | |
def __init__(self, stream): | |
self.stream = stream | |
def write(self, data): | |
self.stream.write(data) | |
self.stream.flush() | |
def __getattr__(self, attr): | |
return getattr(self.stream, attr) | |
if __name__ == '__main__': | |
sys.stdout = Unbuffered(sys.stdout) | |
validate_files(appcast=sys.argv[1], dsa_pubkey=sys.argv[2]) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# Install into $PATH as validate_sparkle_signature | |
set -e | |
OPENSSL='/usr/bin/openssl' | |
DSAPUBKEY="$1" | |
SIGNATURE="$2" | |
ZIPFILE="$3" | |
MKTEMP_TEMPLATE="validate_sparkle_signature.$$.XXXXXXXXX." | |
my_mktemp(){ | |
mktemp -t "${MKTEMP_TEMPLATE}${1}" | |
} | |
DECODED_SIGNATURE_FILE="$(my_mktemp sigfile)" | |
ZIPFILE_SHA1_FILE="$(my_mktemp zipfile_sha1)" | |
usage() { | |
printf '%s DSAPUBKEY SIGNATURE ZIPFILE\n' "$0" | |
echo | |
printf 'Example: %s public_dsa.pem "MCwCFGRnB0iQO97Nzf2Jaq1WIWh1Jym0AhRhfxNTjunEtMxar8naY5wEBvvEow==" my-app.zip\n' "$0" | |
exit | |
} | |
if [ $# != 3 ]; then | |
usage | |
fi | |
echo "$SIGNATURE" | "$OPENSSL" enc -base64 -d > "$DECODED_SIGNATURE_FILE" | |
"$OPENSSL" dgst -sha1 -binary < "$ZIPFILE" > "$ZIPFILE_SHA1_FILE" | |
openssl dgst -sha1 -verify "$DSAPUBKEY" -signature "$DECODED_SIGNATURE_FILE" "$ZIPFILE_SHA1_FILE" | |
rm -f "$DECODED_SIGNATURE_FILE" "$ZIPFILE_SHA1_FILE" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment