add common functions for dealing with apk signatures

This commit is contained in:
Michael Pöhn 2017-09-23 09:36:22 +02:00
parent 45688bfe42
commit efb0bf6ee5
2 changed files with 124 additions and 0 deletions

View file

@ -35,6 +35,7 @@ import hashlib
import socket import socket
import base64 import base64
import zipfile import zipfile
import tempfile
import xml.etree.ElementTree as XMLElementTree import xml.etree.ElementTree as XMLElementTree
from binascii import hexlify from binascii import hexlify
@ -2015,6 +2016,18 @@ def place_srclib(root_dir, number, libpath):
apk_sigfile = re.compile(r'META-INF/[0-9A-Za-z]+\.(SF|RSA|DSA|EC)') apk_sigfile = re.compile(r'META-INF/[0-9A-Za-z]+\.(SF|RSA|DSA|EC)')
def signer_fingerprint_short(sig):
"""Obtain shortened sha256 signing-key fingerprint for pkcs7 signature.
Extracts the first 7 hexadecimal digits of sha256 signing-key fingerprint
for a given pkcs7 signature.
:param sig: Contents of an APK signature.
:returns: shortened signing-key fingerprint.
"""
return signer_fingerprint(sig)[:7]
def signer_fingerprint(sig): def signer_fingerprint(sig):
"""Obtain sha256 signing-key fingerprint for pkcs7 signature. """Obtain sha256 signing-key fingerprint for pkcs7 signature.
@ -2052,6 +2065,18 @@ def apk_signer_fingerprint(apk_path):
return signer_fingerprint(cert) return signer_fingerprint(cert)
def apk_signer_fingerprint_short(apk_path):
"""Obtain shortened sha256 signing-key fingerprint for APK.
Extracts the first 7 hexadecimal digits of sha256 signing-key fingerprint
for a given pkcs7 APK.
:param apk_path: path to APK
:returns: shortened signing-key fingerprint
"""
return apk_signer_fingerprint(apk_path)[:7]
def metadata_get_sigdir(appid, vercode=None): def metadata_get_sigdir(appid, vercode=None):
"""Get signature directory for app""" """Get signature directory for app"""
if vercode: if vercode:
@ -2060,6 +2085,94 @@ def metadata_get_sigdir(appid, vercode=None):
return os.path.join('metadata', appid, 'signatures') return os.path.join('metadata', appid, 'signatures')
def metadata_find_signing_files(appid, vercode):
"""Gets a list of singed manifests and signatures.
:param appid: id string of that app
:param vercode: version code of that app
:returns: a list of triplets for each signing key with following paths:
(signature_file, singed_file, manifest_file)
"""
ret = []
sigdir = metadata_get_sigdir(appid, vercode)
sigs = glob.glob(os.path.join(sigdir, '*.DSA')) + \
glob.glob(os.path.join(sigdir, '*.EC')) + \
glob.glob(os.path.join(sigdir, '*.RSA'))
extre = re.compile('(\.DSA|\.EC|\.RSA)$')
for sig in sigs:
sf = extre.sub('.SF', sig)
if os.path.isfile(sf):
mf = os.path.join(sigdir, 'MANIFEST.MF')
if os.path.isfile(mf):
ret.append((sig, sf, mf))
return ret
def metadata_find_developer_signing_files(appid, vercode):
"""Get developer signature files for specified app from metadata.
:returns: A triplet of paths for signing files from metadata:
(signature_file, singed_file, manifest_file)
"""
allsigningfiles = metadata_find_signing_files(appid, vercode)
if allsigningfiles and len(allsigningfiles) == 1:
return allsigningfiles[0]
else:
return None
def apk_strip_signatures(signed_apk, strip_manifest=False):
"""Removes signatures from APK.
:param signed_apk: path to apk file.
:param strip_manifest: when set to True also the manifest file will
be removed from the APK.
"""
with tempfile.TemporaryDirectory() as tmpdir:
tmp_apk = os.path.join(tmpdir, 'tmp.apk')
os.rename(signed_apk, tmp_apk)
with ZipFile(tmp_apk, 'r') as in_apk:
with ZipFile(signed_apk, 'w') as out_apk:
for f in in_apk.infolist():
if not apk_sigfile.match(f.filename):
if strip_manifest:
if f.filename != 'META-INF/MANIFEST.MF':
buf = in_apk.read(f.filename)
out_apk.writestr(f.filename, buf)
else:
buf = in_apk.read(f.filename)
out_apk.writestr(f.filename, buf)
def apk_implant_signatures(apkpath, signaturefile, signedfile, manifest):
"""Implats a signature from out metadata into an APK.
Note: this changes there supplied APK in place. So copy it if you
need the original to be preserved.
:param apkpath: location of the apk
"""
# get list of available signature files in metadata
with tempfile.TemporaryDirectory() as tmpdir:
# orig_apk = os.path.join(tmpdir, 'orig.apk')
# os.rename(apkpath, orig_apk)
apkwithnewsig = os.path.join(tmpdir, 'newsig.apk')
with ZipFile(apkpath, 'r') as in_apk:
with ZipFile(apkwithnewsig, 'w') as out_apk:
for sig_file in [signaturefile, signedfile, manifest]:
out_apk.write(sig_file, arcname='META-INF/' +
os.path.basename(sig_file))
for f in in_apk.infolist():
if not apk_sigfile.match(f.filename):
if f.filename != 'META-INF/MANIFEST.MF':
buf = in_apk.read(f.filename)
out_apk.writestr(f.filename, buf)
os.remove(apkpath)
p = SdkToolsPopen(['zipalign', '-v', '4', apkwithnewsig, apkpath])
if p.returncode != 0:
raise BuildException("Failed to align application")
def apk_extract_signatures(apkpath, outdir, manifest=True): def apk_extract_signatures(apkpath, outdir, manifest=True):
"""Extracts a signature files from APK and puts them into target directory. """Extracts a signature files from APK and puts them into target directory.

View file

@ -390,6 +390,17 @@ class CommonTest(unittest.TestCase):
self.assertEqual(keytoolcertfingerprint, self.assertEqual(keytoolcertfingerprint,
fdroidserver.common.apk_signer_fingerprint(apkfile)) fdroidserver.common.apk_signer_fingerprint(apkfile))
def test_apk_signer_fingerprint_short(self):
# fingerprints fetched with: keytool -printcert -file ____.RSA
testapks = (('repo/obb.main.oldversion_1444412523.apk', '818e469'),
('repo/obb.main.twoversions_1101613.apk', '32a2362'),
('repo/obb.main.twoversions_1101617.apk', '32a2362'))
for apkfile, keytoolcertfingerprint in testapks:
self.assertEqual(keytoolcertfingerprint,
fdroidserver.common.apk_signer_fingerprint_short(apkfile))
if __name__ == "__main__": if __name__ == "__main__":
parser = optparse.OptionParser() parser = optparse.OptionParser()