Skip to content

Instantly share code, notes, and snippets.

@jessepeterson
Created April 13, 2016 15:55
Show Gist options
  • Save jessepeterson/d9d1f592a8c54395827f73dc60b3a0f3 to your computer and use it in GitHub Desktop.
Save jessepeterson/d9d1f592a8c54395827f73dc60b3a0f3 to your computer and use it in GitHub Desktop.
Create an Apple software distribution manifest for an Apple pkg installer (see http://help.apple.com/deployment/osx/#/ior5df10f73a)
#!/usr/bin/python
# create a "wireless manifest" for Apple pkg installers. for details
# on the manifest see: http://help.apple.com/deployment/osx/#/ior5df10f73a
import subprocess
from tempfile import mkdtemp
import os
from xml.dom.minidom import parse, parseString
from hashlib import md5
import plistlib
# use system PATH
XAR_PATH = 'xar'
MD5_CHUNK_SIZE = 1024 * 1024 * 10 # 10 MiB
def pkg_signed(filename):
xar_args = [XAR_PATH,
'-t', # only test archive
'--dump-toc=-',
'-f',
filename]
p = subprocess.Popen(xar_args, stdout=subprocess.PIPE)
toc, _ = p.communicate()
toc_md = parseString(toc)
# for purposes of checking just see if the xar TOC has an X509Certificate element
return len(toc_md.getElementsByTagName('X509Certificate')) >= 1
def get_pkg_ids(filename):
'''Get metadata from Distribution or PackageInfo inside of pkg'''
tmp_dir = mkdtemp()
print 'Extracting Distribution/PackageInfo file to', tmp_dir
xar_args = [XAR_PATH,
'-x', # extract switch
'--exclude', '/', # exclude any files in subdirectories
'-C', tmp_dir, # extract to our temporary directory
'-f', filename, # extract this specific file
'Distribution', 'PackageInfo'] # files to extract
rtn = subprocess.call(xar_args)
tmp_dist_file = os.path.join(tmp_dir, 'Distribution')
tmp_pinf_file = os.path.join(tmp_dir, 'PackageInfo')
pkgs = []
# for non-PackageInfo packages (use PackageInfo)
if os.path.exists(tmp_dist_file):
# use XML minidom to parse a Distribution file
dist_md = parse(tmp_dist_file)
# capture the pkg IDs and versions by searching for 'pkg-ref' elements
# which include a 'version' attribute on them. append them to our list
for i in dist_md.getElementsByTagName('pkg-ref'):
if i.hasAttribute('version'):
pkgs.append((i.getAttribute('id'), i.getAttribute('version')))
# TODO: to ferret out any actual bundle IDs and use those? by logs for
# commerce/storehelperd daemon appear to look for packages by
# those. but then for packages that don't install bundles?
# dist_md.getElementsByTagName('bundle)
print 'Removing Distribution file'
os.unlink(tmp_dist_file)
# for non-Distribution packages (use the PackageInfo)
if os.path.exists(tmp_pinf_file):
pinf_md = parse(tmp_pinf_file)
# capture the pkg ID and version by searching for a pkg-info element
# and using the identifier and version attributes
for i in pinf_md.getElementsByTagName('pkg-info'):
pkgs.append((i.getAttribute('identifier'), i.getAttribute('version')))
print 'Removing PackageInfo file'
os.unlink(tmp_pinf_file)
print 'Removing temp directory'
os.rmdir(tmp_dir)
return pkgs
def get_chunked_md5(filename, chunksize=10485760):
h = md5()
md5s = []
with open(filename, 'rb') as f:
for chunk in iter(lambda: f.read(chunksize), b''):
new_hash = md5()
new_hash.update(chunk)
md5s.append(new_hash.hexdigest())
return md5s
def gen_package_manifest(filename, url_prefix, title, subtitle=None):
asset = {
'kind': 'software-package',
'md5-size': MD5_CHUNK_SIZE,
'md5s': get_chunked_md5(filename, chunksize=MD5_CHUNK_SIZE),
'url': url_prefix + filename,
}
metadata = {'kind': 'software', 'title': title, 'sizeInBytes': os.path.getsize(filename)}
pkgs_ids = get_pkg_ids(filename)
pkgs_bundles = [{'bundle-identifier': i[0], 'bundle-version': i[1]} for i in pkgs_ids]
if subtitle:
metadata['subtitle'] = subtitle
metadata.update(pkgs_bundles[0])
if len(pkgs_bundles) > 1:
metadata['items'] = pkgs_bundles
download = {'assets': [asset, ], 'metadata': metadata}
manifest = {'items': [download]}
return manifest
PKG = 'munkitools-2.3.1.2535.pkg'
# PKG = 'Server-3.2.2.pkg'
manif = gen_package_manifest(PKG, 'https://example.com/', 'Install Title')
plistlib.writePlist(manif, 'Manifest.xml')
print 'Wrote Manifest.xml'
if not pkg_signed(PKG):
print
print '>>> WARNING! WARNING! Package does not appear to be signed!'
else:
print 'Package appears to be signed (good) but did not check for valid signature or certificate'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment