Skip to content

Instantly share code, notes, and snippets.

@winny-
Last active August 29, 2015 13:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save winny-/10000348 to your computer and use it in GitHub Desktop.
Save winny-/10000348 to your computer and use it in GitHub Desktop.
Validating Sparkle Appcasts
#!/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])
#!/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])
#!/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