Merge "Sign APKs using SHA-256 instead of SHA-1 when possible."
This commit is contained in:
commit
b763b29267
4 changed files with 155 additions and 8 deletions
|
@ -2213,11 +2213,21 @@ define add-carried-jack-resources
|
||||||
fi
|
fi
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
# Returns the minSdkVersion of the specified APK as a decimal number. If the
|
||||||
|
# version is a codename, returns the current platform SDK version (always a
|
||||||
|
# decimal number) instead.
|
||||||
|
#
|
||||||
|
define get-package-min-sdk-version-int
|
||||||
|
$$($(AAPT) dump badging $(1) 2>&1 | grep '^sdkVersion' | cut -d"'" -f2 | \
|
||||||
|
sed -e s/^$(PLATFORM_VERSION_CODENAME)$$/$(PLATFORM_SDK_VERSION)/)
|
||||||
|
endef
|
||||||
|
|
||||||
# Sign a package using the specified key/cert.
|
# Sign a package using the specified key/cert.
|
||||||
#
|
#
|
||||||
define sign-package
|
define sign-package
|
||||||
$(hide) mv $@ $@.unsigned
|
$(hide) mv $@ $@.unsigned
|
||||||
$(hide) java -Djava.library.path=$(SIGNAPK_JNI_LIBRARY_PATH) -jar $(SIGNAPK_JAR) \
|
$(hide) java -Djava.library.path=$(SIGNAPK_JNI_LIBRARY_PATH) -jar $(SIGNAPK_JAR) \
|
||||||
|
--min-sdk-version $(call get-package-min-sdk-version-int,$@.unsigned) \
|
||||||
$(PRIVATE_CERTIFICATE) $(PRIVATE_PRIVATE_KEY) \
|
$(PRIVATE_CERTIFICATE) $(PRIVATE_PRIVATE_KEY) \
|
||||||
$(PRIVATE_ADDITIONAL_CERTIFICATES) $@.unsigned $@.signed
|
$(PRIVATE_ADDITIONAL_CERTIFICATES) $@.unsigned $@.signed
|
||||||
$(hide) mv $@.signed $@
|
$(hide) mv $@.signed $@
|
||||||
|
|
|
@ -213,7 +213,7 @@ embedded_prebuilt_jni_libs := 'lib/*.so'
|
||||||
endif
|
endif
|
||||||
$(built_module): PRIVATE_EMBEDDED_JNI_LIBS := $(embedded_prebuilt_jni_libs)
|
$(built_module): PRIVATE_EMBEDDED_JNI_LIBS := $(embedded_prebuilt_jni_libs)
|
||||||
|
|
||||||
$(built_module) : $(my_prebuilt_src_file) | $(ACP) $(ZIPALIGN) $(SIGNAPK_JAR)
|
$(built_module) : $(my_prebuilt_src_file) | $(ACP) $(ZIPALIGN) $(SIGNAPK_JAR) $(AAPT)
|
||||||
$(transform-prebuilt-to-target)
|
$(transform-prebuilt-to-target)
|
||||||
$(uncompress-shared-libs)
|
$(uncompress-shared-libs)
|
||||||
ifneq ($(LOCAL_CERTIFICATE),PRESIGNED)
|
ifneq ($(LOCAL_CERTIFICATE),PRESIGNED)
|
||||||
|
@ -252,7 +252,7 @@ my_src_dir := $(LOCAL_PATH)/$(my_src_dir)
|
||||||
|
|
||||||
$(built_apk_splits) : PRIVATE_PRIVATE_KEY := $(LOCAL_CERTIFICATE).pk8
|
$(built_apk_splits) : PRIVATE_PRIVATE_KEY := $(LOCAL_CERTIFICATE).pk8
|
||||||
$(built_apk_splits) : PRIVATE_CERTIFICATE := $(LOCAL_CERTIFICATE).x509.pem
|
$(built_apk_splits) : PRIVATE_CERTIFICATE := $(LOCAL_CERTIFICATE).x509.pem
|
||||||
$(built_apk_splits) : $(built_module_path)/%.apk : $(my_src_dir)/%.apk | $(ACP)
|
$(built_apk_splits) : $(built_module_path)/%.apk : $(my_src_dir)/%.apk | $(ACP) $(AAPT)
|
||||||
$(copy-file-to-new-target)
|
$(copy-file-to-new-target)
|
||||||
$(sign-package)
|
$(sign-package)
|
||||||
|
|
||||||
|
|
|
@ -592,7 +592,46 @@ def GetKeyPasswords(keylist):
|
||||||
return key_passwords
|
return key_passwords
|
||||||
|
|
||||||
|
|
||||||
def SignFile(input_name, output_name, key, password, whole_file=False):
|
def GetMinSdkVersion(apk_name):
|
||||||
|
"""Get the minSdkVersion delared in the APK. This can be both a decimal number
|
||||||
|
(API Level) or a codename.
|
||||||
|
"""
|
||||||
|
|
||||||
|
p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
|
||||||
|
output, err = p.communicate()
|
||||||
|
if err:
|
||||||
|
raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
|
||||||
|
% (p.returncode,))
|
||||||
|
|
||||||
|
for line in output.split("\n"):
|
||||||
|
# Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
|
||||||
|
m = re.match(r'sdkVersion:\'([^\']*)\'', line)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
|
raise ExternalError("No minSdkVersion returned by aapt")
|
||||||
|
|
||||||
|
|
||||||
|
def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
|
||||||
|
"""Get the minSdkVersion declared in the APK as a number (API Level). If
|
||||||
|
minSdkVersion is set to a codename, it is translated to a number using the
|
||||||
|
provided map.
|
||||||
|
"""
|
||||||
|
|
||||||
|
version = GetMinSdkVersion(apk_name)
|
||||||
|
try:
|
||||||
|
return int(version)
|
||||||
|
except ValueError:
|
||||||
|
# Not a decimal number. Codename?
|
||||||
|
if version in codename_to_api_level_map:
|
||||||
|
return codename_to_api_level_map[version]
|
||||||
|
else:
|
||||||
|
raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
|
||||||
|
% (version, codename_to_api_level_map))
|
||||||
|
|
||||||
|
|
||||||
|
def SignFile(input_name, output_name, key, password, min_api_level=None,
|
||||||
|
codename_to_api_level_map=dict(),
|
||||||
|
whole_file=False):
|
||||||
"""Sign the input_name zip/jar/apk, producing output_name. Use the
|
"""Sign the input_name zip/jar/apk, producing output_name. Use the
|
||||||
given key and password (the latter may be None if the key does not
|
given key and password (the latter may be None if the key does not
|
||||||
have a password.
|
have a password.
|
||||||
|
@ -600,6 +639,13 @@ def SignFile(input_name, output_name, key, password, whole_file=False):
|
||||||
If whole_file is true, use the "-w" option to SignApk to embed a
|
If whole_file is true, use the "-w" option to SignApk to embed a
|
||||||
signature that covers the whole file in the archive comment of the
|
signature that covers the whole file in the archive comment of the
|
||||||
zip file.
|
zip file.
|
||||||
|
|
||||||
|
min_api_level is the API Level (int) of the oldest platform this file may end
|
||||||
|
up on. If not specified for an APK, the API Level is obtained by interpreting
|
||||||
|
the minSdkVersion attribute of the APK's AndroidManifest.xml.
|
||||||
|
|
||||||
|
codename_to_api_level_map is needed to translate the codename which may be
|
||||||
|
encountered as the APK's minSdkVersion.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
java_library_path = os.path.join(
|
java_library_path = os.path.join(
|
||||||
|
@ -612,6 +658,15 @@ def SignFile(input_name, output_name, key, password, whole_file=False):
|
||||||
cmd.extend(OPTIONS.extra_signapk_args)
|
cmd.extend(OPTIONS.extra_signapk_args)
|
||||||
if whole_file:
|
if whole_file:
|
||||||
cmd.append("-w")
|
cmd.append("-w")
|
||||||
|
|
||||||
|
min_sdk_version = min_api_level
|
||||||
|
if min_sdk_version is None:
|
||||||
|
if not whole_file:
|
||||||
|
min_sdk_version = GetMinSdkVersionInt(
|
||||||
|
input_name, codename_to_api_level_map)
|
||||||
|
if min_sdk_version is not None:
|
||||||
|
cmd.extend(["--min-sdk-version", str(min_sdk_version)])
|
||||||
|
|
||||||
cmd.extend([key + OPTIONS.public_key_suffix,
|
cmd.extend([key + OPTIONS.public_key_suffix,
|
||||||
key + OPTIONS.private_key_suffix,
|
key + OPTIONS.private_key_suffix,
|
||||||
input_name, output_name])
|
input_name, output_name])
|
||||||
|
|
|
@ -127,14 +127,34 @@ def CheckAllApksSigned(input_tf_zip, apk_key_map):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def SignApk(data, keyname, pw):
|
def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map):
|
||||||
unsigned = tempfile.NamedTemporaryFile()
|
unsigned = tempfile.NamedTemporaryFile()
|
||||||
unsigned.write(data)
|
unsigned.write(data)
|
||||||
unsigned.flush()
|
unsigned.flush()
|
||||||
|
|
||||||
signed = tempfile.NamedTemporaryFile()
|
signed = tempfile.NamedTemporaryFile()
|
||||||
|
|
||||||
common.SignFile(unsigned.name, signed.name, keyname, pw)
|
# For pre-N builds, don't upgrade to SHA-256 JAR signatures based on the APK's
|
||||||
|
# minSdkVersion to avoid increasing incremental OTA update sizes. If an APK
|
||||||
|
# didn't change, we don't want its signature to change due to the switch
|
||||||
|
# from SHA-1 to SHA-256.
|
||||||
|
# By default, APK signer chooses SHA-256 signatures if the APK's minSdkVersion
|
||||||
|
# is 18 or higher. For pre-N builds we disable this mechanism by pretending
|
||||||
|
# that the APK's minSdkVersion is 1.
|
||||||
|
# For N+ builds, we let APK signer rely on the APK's minSdkVersion to
|
||||||
|
# determine whether to use SHA-256.
|
||||||
|
min_api_level = None
|
||||||
|
if platform_api_level > 23:
|
||||||
|
# Let APK signer choose whether to use SHA-1 or SHA-256, based on the APK's
|
||||||
|
# minSdkVersion attribute
|
||||||
|
min_api_level = None
|
||||||
|
else:
|
||||||
|
# Force APK signer to use SHA-1
|
||||||
|
min_api_level = 1
|
||||||
|
|
||||||
|
common.SignFile(unsigned.name, signed.name, keyname, pw,
|
||||||
|
min_api_level=min_api_level,
|
||||||
|
codename_to_api_level_map=codename_to_api_level_map)
|
||||||
|
|
||||||
data = signed.read()
|
data = signed.read()
|
||||||
unsigned.close()
|
unsigned.close()
|
||||||
|
@ -144,7 +164,8 @@ def SignApk(data, keyname, pw):
|
||||||
|
|
||||||
|
|
||||||
def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
|
def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
|
||||||
apk_key_map, key_passwords):
|
apk_key_map, key_passwords, platform_api_level,
|
||||||
|
codename_to_api_level_map):
|
||||||
|
|
||||||
maxsize = max([len(os.path.basename(i.filename))
|
maxsize = max([len(os.path.basename(i.filename))
|
||||||
for i in input_tf_zip.infolist()
|
for i in input_tf_zip.infolist()
|
||||||
|
@ -200,7 +221,8 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
|
||||||
key = apk_key_map[name]
|
key = apk_key_map[name]
|
||||||
if key not in common.SPECIAL_CERT_STRINGS:
|
if key not in common.SPECIAL_CERT_STRINGS:
|
||||||
print " signing: %-*s (%s)" % (maxsize, name, key)
|
print " signing: %-*s (%s)" % (maxsize, name, key)
|
||||||
signed_data = SignApk(data, key, key_passwords[key])
|
signed_data = SignApk(data, key, key_passwords[key], platform_api_level,
|
||||||
|
codename_to_api_level_map)
|
||||||
common.ZipWriteStr(output_tf_zip, out_info, signed_data)
|
common.ZipWriteStr(output_tf_zip, out_info, signed_data)
|
||||||
else:
|
else:
|
||||||
# an APK we're not supposed to sign.
|
# an APK we're not supposed to sign.
|
||||||
|
@ -440,6 +462,57 @@ def BuildKeyMap(misc_info, key_mapping_options):
|
||||||
OPTIONS.key_map[s] = d
|
OPTIONS.key_map[s] = d
|
||||||
|
|
||||||
|
|
||||||
|
def GetApiLevelAndCodename(input_tf_zip):
|
||||||
|
data = input_tf_zip.read("SYSTEM/build.prop")
|
||||||
|
api_level = None
|
||||||
|
codename = None
|
||||||
|
for line in data.split("\n"):
|
||||||
|
line = line.strip()
|
||||||
|
original_line = line
|
||||||
|
if line and line[0] != '#' and "=" in line:
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
key = key.strip()
|
||||||
|
if key == "ro.build.version.sdk":
|
||||||
|
api_level = int(value.strip())
|
||||||
|
elif key == "ro.build.version.codename":
|
||||||
|
codename = value.strip()
|
||||||
|
|
||||||
|
if api_level is None:
|
||||||
|
raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
|
||||||
|
if codename is None:
|
||||||
|
raise ValueError("No ro.build.version.codename in SYSTEM/build.prop")
|
||||||
|
|
||||||
|
return (api_level, codename)
|
||||||
|
|
||||||
|
|
||||||
|
def GetCodenameToApiLevelMap(input_tf_zip):
|
||||||
|
data = input_tf_zip.read("SYSTEM/build.prop")
|
||||||
|
api_level = None
|
||||||
|
codenames = None
|
||||||
|
for line in data.split("\n"):
|
||||||
|
line = line.strip()
|
||||||
|
original_line = line
|
||||||
|
if line and line[0] != '#' and "=" in line:
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
key = key.strip()
|
||||||
|
if key == "ro.build.version.sdk":
|
||||||
|
api_level = int(value.strip())
|
||||||
|
elif key == "ro.build.version.all_codenames":
|
||||||
|
codenames = value.strip().split(",")
|
||||||
|
|
||||||
|
if api_level is None:
|
||||||
|
raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
|
||||||
|
if codenames is None:
|
||||||
|
raise ValueError("No ro.build.version.all_codenames in SYSTEM/build.prop")
|
||||||
|
|
||||||
|
result = dict()
|
||||||
|
for codename in codenames:
|
||||||
|
codename = codename.strip()
|
||||||
|
if len(codename) > 0:
|
||||||
|
result[codename] = api_level
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
|
|
||||||
key_mapping_options = []
|
key_mapping_options = []
|
||||||
|
@ -498,8 +571,17 @@ def main(argv):
|
||||||
CheckAllApksSigned(input_zip, apk_key_map)
|
CheckAllApksSigned(input_zip, apk_key_map)
|
||||||
|
|
||||||
key_passwords = common.GetKeyPasswords(set(apk_key_map.values()))
|
key_passwords = common.GetKeyPasswords(set(apk_key_map.values()))
|
||||||
|
platform_api_level, platform_codename = GetApiLevelAndCodename(input_zip)
|
||||||
|
codename_to_api_level_map = GetCodenameToApiLevelMap(input_zip)
|
||||||
|
# Android N will be API Level 24, but isn't yet.
|
||||||
|
# TODO: Remove this workaround once Android N is officially API Level 24.
|
||||||
|
if platform_api_level == 23 and platform_codename == "N":
|
||||||
|
platform_api_level = 24
|
||||||
|
|
||||||
ProcessTargetFiles(input_zip, output_zip, misc_info,
|
ProcessTargetFiles(input_zip, output_zip, misc_info,
|
||||||
apk_key_map, key_passwords)
|
apk_key_map, key_passwords,
|
||||||
|
platform_api_level,
|
||||||
|
codename_to_api_level_map)
|
||||||
|
|
||||||
common.ZipClose(input_zip)
|
common.ZipClose(input_zip)
|
||||||
common.ZipClose(output_zip)
|
common.ZipClose(output_zip)
|
||||||
|
|
Loading…
Reference in a new issue