android ota 版本校验,OTA升级签名校验简析
1. 概要
如果進(jìn)行過OTA升級的開發(fā)者,都或多或少有這樣的疑問,如何確定該OTA升級包是可以信任的呢?這其中其實(shí)涉及到一個簽名驗(yàn)證的流程。
2. 簽名生成
在生成正規(guī)的固件時,一般會運(yùn)行生成新key的腳本,并重新修改key中的信息。以網(wǎng)上常用的生成key的腳本為例:
#!/bin/sh
AUTH='/C=CN/ST=xxxx/L=xxxxx/O=xxxxxx/OU=xxxxx/CN=China/emailAddress=xxxxxxx@com'
openssl genrsa -3 -out $1.pem 2048
openssl req -new -x509 -key $1.pem -out $1.x509.pem -days 10000 \
-subj "$AUTH"
echo "Please enter the password for this key:"
openssl pkcs8 -in $1.pem -topk8 -outform DER -out $1.pk8 -passout stdin
其中openssl通過genrsa標(biāo)準(zhǔn)命令生成私鑰,默認(rèn)大小為2048:
openssl genrsa -3 -out $1.pem 2048
生成私鑰后,生成證書簽署請求,即公鑰:
openssl req -new -x509 -key $1.pem -out $1.x509.pem -days 365 \
-subj "$AUTH"
-new: new request
-x509: output a x509 structure instead of a cert. req.該選項(xiàng)說明生成一個自簽名的證書。
-keyfile: use the private key contained in file
-days: number of days a certificate generated by -x509 is valid for.
最后通過私鑰pem文件生成PKCS8私鑰文件
openssl pkcs8 -in $1.pem -topk8 -outform DER -out $1.pk8 -passout stdin
也可以參考android原生的生成key的流程,位于android/development/tools下的make_key腳本i。
自此后,生成了一對公私鑰用于簽名校驗(yàn)。
3. sign_target_files_apks.py
3.1 對Apk進(jìn)行重簽名流程
sign_target_files_apks在本人之前的用法都局限于將targetfile里的apk進(jìn)行重簽名,其流程如下:
1.獲取腳本輸入?yún)?shù)
2.獲取輸入文件以及輸出文件參數(shù),讀取misc_info文件
input_zip = zipfile.ZipFile(args[0], "r")
output_zip = zipfile.ZipFile(args[1], "w")
misc_info = common.LoadInfoDict(input_zip)
misc_info記錄了一些參數(shù),如下:
其中這里關(guān)注的是默認(rèn)簽名的路徑:
default_system_dev_certificate=build/target/product/security/testkey
3.建立key映射
假如在調(diào)用腳本時未指定-d.-k參數(shù),那么默認(rèn)使用的正是系統(tǒng)自帶的testkey。否則,將會映射到指定key目錄下的Key
def BuildKeyMap(misc_info, key_mapping_options):
for s, d in key_mapping_options:
if s is None: # -d option
devkey = misc_info.get("default_system_dev_certificate",
"build/target/product/security/testkey")
devkeydir = os.path.dirname(devkey)
OPTIONS.key_map.update({
devkeydir + "/testkey": d + "/releasekey",
devkeydir + "/devkey": d + "/releasekey",
devkeydir + "/media": d + "/media",
devkeydir + "/shared": d + "/shared",
devkeydir + "/platform": d + "/platform",
})
else:
OPTIONS.key_map[s] = d
4.讀取targetfile中的證書文件
apk_key_map = GetApkCerts(input_zip)
GetApkCerts的實(shí)現(xiàn)如下,其實(shí)質(zhì)是讀取了targetfile中/META/apkcerts.txt文件
def GetApkCerts(tf_zip):
certmap = common.ReadApkCerts(tf_zip)
# apply the key remapping to the contents of the file
for apk, cert in certmap.iteritems():
certmap[apk] = OPTIONS.key_map.get(cert, cert)
# apply all the -e options, overriding anything in the file
for apk, cert in OPTIONS.extra_apks.iteritems():
if not cert:
cert = "PRESIGNED"
certmap[apk] = OPTIONS.key_map.get(cert, cert)
return certmap
def ReadApkCerts(tf_zip):
"""Given a target_files ZipFile, parse the META/apkcerts.txt file
and return a {package: cert} dict."""
certmap = {}
for line in tf_zip.read("META/apkcerts.txt").split("\n"):
line = line.strip()
if not line:
continue
m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
r'private_key="(.*)"$', line)
if m:
name, cert, privkey = m.groups()
public_key_suffix_len = len(OPTIONS.public_key_suffix)
private_key_suffix_len = len(OPTIONS.private_key_suffix)
if cert in SPECIAL_CERT_STRINGS and not privkey:
certmap[name] = cert
elif (cert.endswith(OPTIONS.public_key_suffix) and
privkey.endswith(OPTIONS.private_key_suffix) and
cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
certmap[name] = cert[:-public_key_suffix_len]
else:
raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
return certmap
在這里可以分析下apkcerts文件,其內(nèi)容格式如下:
name="RecoveryLocalizer.apk" certificate="build/target/product/security/testkey.x509.pem" private_key="build/target/product/security/testkey.pk8"
name="CtsVerifier.apk" certificate="build/target/product/security/testkey.x509.pem" private_key="build/target/product/security/testkey.pk8"
....
ame="CtsShimPrivUpgradePrebuilt.apk" certificate="PRESIGNED" private_key=""
name="CtsShimPrivUpgradeWrongSHAPrebuilt.apk" certificate="PRESIGNED" private_key=""
...
可以看出每一個apk中,都指定了證書的位置以及私鑰文件路徑,可以對比出,當(dāng)應(yīng)用的Android.mk中以platform簽名,其格式為:
LOCAL_CERTIFICATE := platform
name="HdmiCts.apk" certificate="build/target/product/security/platform.x509.pem" private_key="build/target/product/security/platform.pk8"
一般apk以presigned簽名的,則為:
LOCAL_CERTIFICATE := PRESIGNED
name="AllCast.apk" certificate="PRESIGNED" private_key=""
以media簽名的,則為:
LOCAL_CERTIFICATE := media
name="Gallery.apk" certificate="build/target/product/security/media.x509.pem" private_key="build/target/product/security/media.pk8"
所以該文件定義了每個文件的證書以及簽名情況,如果回到編譯系統(tǒng),可以看出該文件的編譯規(guī)則:
APKCERTS_FILE := $(intermediates)/$(name).txt
$(APKCERTS_FILE):
@echo APK certs list: $@
@mkdir -p $(dir $@)
@rm -f $@
$(foreach p,$(PACKAGES),\
$(if $(PACKAGES.$(p).EXTERNAL_KEY),\
$(call _apkcerts_echo_with_newline,\
'name="$(p).apk" certificate="EXTERNAL" \
private_key=""' >> $@),\
$(call _apkcerts_echo_with_newline,\
'name="$(p).apk" certificate="$(PACKAGES.$(p).CERTIFICATE)" \
private_key="$(PACKAGES.$(p).PRIVATE_KEY)"' >> $@)))
# In case value of PACKAGES is empty.
$(hide) touch $@
.PHONY: apkcerts-list
apkcerts-list: $(APKCERTS_FILE)
可以看出編譯偽目標(biāo)apkcerts-list時,編譯系統(tǒng)就會遍歷$(PACKAGES),并將apk的信息記錄在apkcerts.txt文檔里。
在編譯每一個Apk時,package_internal.mk會讀取LOCAL_CERTIFICATE參數(shù),并記錄信息如下:
PACKAGES.$(LOCAL_PACKAGE_NAME).PRIVATE_KEY := $(private_key)
PACKAGES.$(LOCAL_PACKAGE_NAME).CERTIFICATE := $(certificate)
在編譯targetfiles的時候,會編譯該文件:
$(BUILT_TARGET_FILES_PACKAGE): \
$(INSTALLED_BOOTIMAGE_TARGET) \
$(INSTALLED_RADIOIMAGE_TARGET) \
$(INSTALLED_RECOVERYIMAGE_TARGET) \
$(INSTALLED_SYSTEMIMAGE) \
$(INSTALLED_USERDATAIMAGE_TARGET) \
$(INSTALLED_CACHEIMAGE_TARGET) \
$(INSTALLED_VENDORIMAGE_TARGET) \
$(INSTALLED_ANDROID_INFO_TXT_TARGET) \
$(SELINUX_FC) \
$(APKCERTS_FILE) \
$(HOST_OUT_EXECUTABLES)/fs_config \
| $(ACP)
5.處理targetfile中文件
對于targetfile中文件,核心部分調(diào)用如下方法:
ProcessTargetFiles(input_zip, output_zip, misc_info,
apk_key_map, key_passwords,
platform_api_level,
codename_to_api_level_map)
主要關(guān)心Apk部分:
for info in input_tf_zip.infolist():
if info.filename.startswith("IMAGES/"):
continue
...
# Sign APKs.
if info.filename.endswith(".apk"):
name = os.path.basename(info.filename)
key = apk_key_map[name]
if key not in common.SPECIAL_CERT_STRINGS:
print " signing: %-*s (%s)" % (maxsize, name, 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)
else:
# an APK we're not supposed to sign.
print "NOT signing: %s" % (name,)
common.ZipWriteStr(output_tf_zip, out_info, data)
...
只要apk的key不是以"PRESIGNED"或者"EXTERNAL"簽名的,都會去重新簽名:
SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
3.2 更新system,recovery簽名
當(dāng)sign_target_files_apks指定了-O參數(shù)時,將會執(zhí)行如下邏輯:
if OPTIONS.replace_ota_keys:
new_recovery_keys = ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info)
if new_recovery_keys:
write_to_temp("RECOVERY/RAMDISK/res/keys", 0o755 << 16, new_recovery_keys)
即當(dāng)制定了-O參數(shù)時,將會調(diào)用ReplaceOtaKeys方法。
獲取/META/otakeys.txt
當(dāng)方案中定義了PRODUCT_OTA_PUBLIC_KEYS時,在編譯時會將內(nèi)容寫入otakeys.txt文件中
try:
keylist = input_tf_zip.read("META/otakeys.txt").split()
except KeyError:
raise common.ExternalError("can't read META/otakeys.txt from input")
2.獲取recovery的證書
同理,如果在方案中制定了extra_recovery_keys,也會從misc_info中找證書
extra_recovry_keys = misc_info.get("extra_recovery_keys", None)
if extra_recovery_keys:
extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
for k in extra_recovery_keys.split()]
if extra_recovery_keys:
print "extra recovery-only key(s): " + ", ".join(extra_recovery_keys)
else:
extra_recovery_keys = []
3.對mapped_keys賦值
mapped_keys = []
for k in keylist:
m = re.match(r"^(.*)\.x509\.pem$", k)
if not m:
raise common.ExternalError(
"can't parse \"%s\" from META/otakeys.txt" % (k,))
k = m.group(1)
mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")
if mapped_keys:
print "using:\n ", "\n ".join(mapped_keys)
print "for OTA package verification"
else:
devkey = misc_info.get("default_system_dev_certificate",
"build/target/product/security/testkey")
mapped_keys.append(
OPTIONS.key_map.get(devkey, devkey) + ".x509.pem")
print "META/otakeys.txt has no keys; using", mapped_keys[0]
假如otakey文件中有內(nèi)容,則將第一個key添加到mapped_keys中。否則就默認(rèn)為系統(tǒng)的testkey。
4.利用dumpkey.jar為recovery創(chuàng)建新的key
p = common.Run(["java", "-jar",
os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")]
+ mapped_keys + extra_recovery_keys,
stdout=subprocess.PIPE)
new_recovery_keys, _ = p.communicate()
if p.returncode != 0:
raise common.ExternalError("failed to run dumpkeys")
common.ZipWriteStr(output_tf_zip, "RECOVERY/RAMDISK/res/keys",
new_recovery_keys)
通過之前的extra_recovery_keys作為參數(shù),將公鑰內(nèi)容打印出來,dumpkey.jar的內(nèi)容如下:
5.更新otacerts.zip
最后會將mapped_keys中的文件寫入otacerts.zip中。如果otakey為空,則默認(rèn)為testkey.x509.pem,如果指定了key的路徑(-d),以及設(shè)置了-O,那么由于testkey通過方法BuildKeyMap綁定了releasekey,因此會替換為releasekey.x509.pem
temp_file = cStringIO.StringIO()
certs_zip = zipfile.ZipFile(temp_file, "w")
for k in mapped_keys:
common.ZipWrite(certs_zip, k)
common.ZipClose(certs_zip)
common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip",
temp_file.getvalue())
4. ota_from_target_files.py
在上述步驟中,通過對targetfiles中的otacerts.zip以及recovery的/res/keys進(jìn)行更新后,生成出來的固件假如與后續(xù)的ota包簽名不符,那么在校驗(yàn)的時候也是會失敗,所以在生成ota包時,也必須指定相應(yīng)的公鑰。
在ota_from_target_files中有如下的解析:
-k (--package_key) Key to use to sign the package (default is
the value of default_system_dev_certificate from the input
target-files's META/misc_info.txt, or
"build/target/product/security/testkey" if that value is not
specified).
并且在選項(xiàng)中假如未定義"--no_signing",而且-k未指定,將會使用原生的的testkey。
# Use the default key to sign the package if not specified with package_key.
if not OPTIONS.no_signing:
if OPTIONS.package_key is None:
OPTIONS.package_key = OPTIONS.info_dict.get(
"default_system_dev_certificate",
"build/target/product/security/testkey")
當(dāng)OTA包完成所有的打包工作后,最終會調(diào)用到如下方法,表明要對整包進(jìn)行簽名。
# Sign the whole package to comply with the Android OTA package format.
def SignOutput(temp_zip_name, output_zip_name)
其實(shí)現(xiàn)如下:
def SignOutput(temp_zip_name, output_zip_name):
key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
pw = key_passwords[OPTIONS.package_key]
common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
whole_file=True)
common.GetKeyPasswords將會提示用戶輸入密碼,假如密碼與之前創(chuàng)建公鑰私鑰的密碼不對,則提示錯誤,匹配才能往下進(jìn)行,并返回更新后{key:password}
根據(jù)ota_from_targetfiles中指定的package_key,找到其密碼pw
調(diào)用SignFile對OTA包進(jìn)行整包簽名
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
given key and password (the latter may be None if the key does not
have a password.
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
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(
OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
cmd = [OPTIONS.java_path, OPTIONS.java_args,
"-Djava.library.path=" + java_library_path,
"-jar",
os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
cmd.extend(OPTIONS.extra_signapk_args)
if whole_file:
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,
key + OPTIONS.private_key_suffix,
input_name, output_name])
p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
if password is not None:
password += "\n"
p.communicate(password)
if p.returncode != 0:
raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
這里實(shí)際進(jìn)行的命令是:
java -Xmx2048m -Djava.library.path=$ANDROID_BUILD_TOP/out/host/linux-x86/lib64 -jar $ANDROID_BUILD_TOP/out/host/linux-x86/framework/signapk.jar -w $ANDROID_BUILD_TOP/build/target/product/security/testkey.x509.pem $ANDROID_BUILD_TOP/build/target/product/security/testkey.pk8 $1 $2
通過反編譯可以看出對ota包的簽名實(shí)際操作如下:
計(jì)算出key的個數(shù),這里由于公鑰私鑰成對出現(xiàn),而且指定輸入輸出文件,那么key的對數(shù)即為在-w之后的個數(shù)除以2,再減去1,ota簽名時numKeys為1.
int numKeys = (args.length - argstart) / 2 - 1;
if ((signWholeFile) && (numKeys > 1)) {//這里證明了可以的對數(shù)只能為1
System.err.println("Only one key may be used with -w.");
System.exit(2);
}
載入輸入文件和輸出文件參數(shù)
String inputFilename = args[(args.length - 2)];
String outputFilename = args[(args.length - 1)];
inputJar = new JarFile(new File(inputFilename), false);
outputFile = new FileOutputStream(outputFilename);
讀取公鑰內(nèi)容
File firstPublicKeyFile = new File(args[(argstart + 0)]);//獲取公鑰文件
X509Certificate[] publicKey = new X509Certificate[numKeys];//新建X509證書
try {
for (int i = 0; i < numKeys; i++) {
int argNum = argstart + i * 2;
publicKey[i] = readPublicKey(new File(args[argNum]));//將公鑰讀取到X509結(jié)構(gòu)的publicKey中
hashes |= getDigestAlgorithm(publicKey[i], minSdkVersion);//計(jì)算摘要
}
} catch (IllegalArgumentException e) {
System.err.println(e);
System.exit(1);
}
讀取私鑰內(nèi)容
timestamp -= TimeZone.getDefault().getOffset(timestamp);
PrivateKey[] privateKey = new PrivateKey[numKeys];
for (int i = 0; i < numKeys; i++) {
int argNum = argstart + i * 2 + 1;
privateKey[i] = readPrivateKey(new File(args[argNum]));
}
進(jìn)行簽名
signWholeFile(inputJar, firstPublicKeyFile, publicKey[0], privateKey[0], timestamp, minSdkVersion, outputFile);
其實(shí)現(xiàn)如下:
CMSSigner cmsOut = new CMSSigner(inputJar, publicKeyFile, publicKey, privateKey, timestamp, minSdkVersion, outputStream);
ByteArrayOutputStream temp = new ByteArrayOutputStream();
byte[] message = "signed by SignApk".getBytes("UTF-8");
temp.write(message);//將message寫入輸出流
temp.write(0);
cmsOut.writeSignatureBlock(temp);
byte[] zipData = cmsOut.getSigner().getTail();
//檢查zip格式核心目錄結(jié)束標(biāo)識是否為504B0506
if ((zipData[(zipData.length - 22)] != 80) || (zipData[(zipData.length - 21)] != 75) || (zipData[(zipData.length - 20)] != 5) || (zipData[(zipData.length - 19)] != 6))
{
throw new IllegalArgumentException("zip data already has an archive comment");
}
int total_size = temp.size() + 6;
//檢查簽名大小
if (total_size > 65535) {
throw new IllegalArgumentException("signature is too big for ZIP file comment");
}
//結(jié)尾格式以2字節(jié)`signature_staret` ff ff 2字節(jié)`total_size`結(jié)尾
int signature_start = total_size - message.length - 1;
temp.write(signature_start & 0xFF);
temp.write(signature_start >> 8 & 0xFF);
temp.write(255);
temp.write(255);
temp.write(total_size & 0xFF);
temp.write(total_size >> 8 & 0xFF);
temp.flush();
//檢查temp流的結(jié)尾格式
byte[] b = temp.toByteArray();
for (int i = 0; i < b.length - 3; i++) {
if ((b[i] == 80) && (b[(i + 1)] == 75) && (b[(i + 2)] == 5) && (b[(i + 3)] == 6)) {
throw new IllegalArgumentException("found spurious EOCD header at " + i);
}
}
outputStream.write(total_size & 0xFF);
outputStream.write(total_size >> 8 & 0xFF);
temp.writeTo(outputStream);//將temp流寫到輸出文件中
5. 校驗(yàn)OTA包
5.1 RecoverySystem校驗(yàn)OTA包
RecoverySystem中有校驗(yàn)OTA包的接口
public static void verifyPackage(File packageFile,
ProgressListener listener,
File deviceCertsZipFile)
其校驗(yàn)流程如下:
1.獲取OTA包
final RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
2.校驗(yàn)OTA包的尾部
raf.seek(fileLen - 6);
byte[] footer = new byte[6];
raf.readFully(footer);
//校驗(yàn)后6字節(jié)中間的兩個字節(jié)是否為ff,與signapk.jar邏輯相同
if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) {
throw new SignatureException("no signature in file (no footer)");
}
//獲取commentSize以及signatureStart
final int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
final int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
byte[] eocd = new byte[commentSize + 22];
raf.seek(fileLen - (commentSize + 22));
raf.readFully(eocd);
// Check that we have found the start of the
// end-of-central-directory record.
//檢查核心標(biāo)識是否為504b0506
if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b ||
eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) {
throw new SignatureException("no signature in file (bad footer)");
}
//檢查eocd后四個字節(jié)是否是EOCD標(biāo)識,如果是則報(bào)錯
for (int i = 4; i < eocd.length-3; ++i) {
if (eocd[i ] == (byte)0x50 && eocd[i+1] == (byte)0x4b &&
eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) {
throw new SignatureException("EOCD marker found after start of EOCD");
}
}
3.從OT包中獲取證書
// Parse the signature
PKCS7 block =
new PKCS7(new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));
//獲取證書
// Take the first certificate from the signature (packages
// should contain only one).
X509Certificate[] certificates = block.getCertificates();
if (certificates == null || certificates.length == 0) {
throw new SignatureException("signature contains no certificates");
}
X509Certificate cert = certificates[0];
//獲取公鑰
PublicKey signatureKey = cert.getPublicKey();
SignerInfo[] signerInfos = block.getSignerInfos();
if (signerInfos == null || signerInfos.length == 0) {
throw new SignatureException("signature contains no signedData");
}
SignerInfo signerInfo = signerInfos[0];
4.對比公鑰信息
// Check that the public key of the certificate contained
// in the package equals one of our trusted public keys.
boolean verified = false;
HashSet trusted = getTrustedCerts(
deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
for (X509Certificate c : trusted) {
if (c.getPublicKey().equals(signatureKey)) {
verified = true;
break;
}
}
if (!verified) {
throw new SignatureException("signature doesn't match any trusted key");
}
確保OTA包中獲取的公鑰是在信任的keylist之中,信任的keylist是從以下目錄獲取的:
private static final File DEFAULT_KEYSTORE =
new File("/system/etc/security/otacerts.zip");
這個文件就是在sign_target_files_apks.py中通過指定-O選項(xiàng)時更新的。假如沒有指定新的簽名目錄,那么使用原生的testkey作為密鑰。所以從校驗(yàn)可以看出,至少對ota整包的簽名的公鑰信息必須要與待OTA升級的system/etc/otacerts.zip中的公鑰信息是要一致的,否則將在校驗(yàn)時出錯。
這個校驗(yàn)流程與常規(guī)的簽名校驗(yàn)一致,假如加密文件,這里OTA包,使用了私鑰進(jìn)行簽名,并在尾部附上公鑰,那么正常而言,使用該公鑰即可對其進(jìn)行驗(yàn)證,但是這里就多了一個流程是公鑰必須要和需要升級的固件是一致的,相當(dāng)于CA的一個作用證明該公鑰是有效的,才會繼續(xù)使用公鑰去計(jì)算出OTA包中的摘要,然后通過該摘要值與給到的OTA包進(jìn)行計(jì)算的摘要值進(jìn)行對比,保證該OTA包是沒有經(jīng)過修改的。
5.對比摘要值
SignerInfo verifyResult = block.verify(signerInfo, new InputStream
這里的veriry中還實(shí)現(xiàn)了一個read方法,應(yīng)當(dāng)是對ota包中的內(nèi)容進(jìn)行讀取,并與signerinfo的信息進(jìn)行比較,其中read的內(nèi)容范圍為:
// The signature covers all of the OTA package except the
// archive comment and its 2-byte length.
long toRead = fileLen - commentSize - 2;
當(dāng)校驗(yàn)結(jié)果為null時出錯
if (verifyResult == null) {
throw new SignatureException("signature digest verification failed");
}
5.2 recovery校驗(yàn)OTA包
recovery的校驗(yàn)流程與RecoverySystem中相仿。
6. 結(jié)論
一般而言,正常編譯OTA包與編譯固件,均是默認(rèn)使用testkey進(jìn)行簽名
假如指定了新的簽名目錄,那么必須保證,待升級的固件中的公鑰信息(包括system以及recovery)都要與升級的OTA包中的公鑰信息等匹配,即使用同一對密鑰對。
總結(jié)
以上是生活随笔為你收集整理的android ota 版本校验,OTA升级签名校验简析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android 如何去掉自定义标签页,A
- 下一篇: android 代码设置alignlef