OTA升級簽名校驗簡析

1. 概要

若是進行過OTA升級的開發者,都或多或少有這樣的疑問,如何肯定該OTA升級包是能夠信任的呢?這其中其實涉及到一個簽名驗證的流程。java

2. 簽名生成

在生成正規的固件時,通常會運行生成新key的腳本,並從新修改key中的信息。以網上經常使用的生成key的腳本爲例:linux

#!/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標準命令生成私鑰,默認大小爲2048:android

openssl genrsa -3 -out $1.pem 2048

生成私鑰後,生成證書籤署請求,即公鑰:api

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.該選項說明生成一個自簽名的證書。
  • -keyfile: use the private key contained in file
  • -days: number of days a certificate generated by -x509 is valid for.

最後經過私鑰pem文件生成PKCS8私鑰文件app

openssl pkcs8 -in $1.pem -topk8 -outform DER -out $1.pk8 -passout stdin

也能夠參考android原生的生成key的流程,位於android/development/tools下的make_key腳本i。
自此後,生成了一對公私鑰用於簽名校驗。dom

3. sign_target_files_apks.py

3.1 對Apk進行重簽名流程

sign_target_files_apks在本人以前的用法都侷限於將targetfile裏的apk進行重簽名,其流程以下:ide

1.獲取腳本輸入參數
2.獲取輸入文件以及輸出文件參數,讀取misc_info文件ui

input_zip = zipfile.ZipFile(args[0], "r")
  output_zip = zipfile.ZipFile(args[1], "w")
  misc_info = common.LoadInfoDict(input_zip)

misc_info記錄了一些參數,以下:this

其中這裏關注的是默認簽名的路徑:加密

default_system_dev_certificate=build/target/product/security/testkey

3.創建key映射

假如在調用腳本時未指定-d.-k參數,那麼默認使用的正是系統自帶的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的實現以下,其實質是讀取了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文件,其內容格式以下:

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中,都指定了證書的位置以及私鑰文件路徑,能夠對比出,當應用的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"

因此該文件定義了每一個文件的證書以及簽名狀況,若是回到編譯系統,能夠看出該文件的編譯規則:

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)

能夠看出編譯僞目標apkcerts-list時,編譯系統就會遍歷$(PACKAGES),並將apk的信息記錄在apkcerts.txt文檔裏。
在編譯每個Apk時,package_internal.mk會讀取LOCAL_CERTIFICATE參數,並記錄信息以下:

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中文件,核心部分調用以下方法:

ProcessTargetFiles(input_zip, output_zip, misc_info,
                     apk_key_map, key_passwords,
                     platform_api_level,
                     codename_to_api_level_map)

主要關心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簽名

sign_target_files_apks指定了-O參數時,將會執行以下邏輯:

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)

即當制定了-O參數時,將會調用ReplaceOtaKeys方法。

  1. 獲取/META/otakeys.txt

當方案中定義了PRODUCT_OTA_PUBLIC_KEYS時,在編譯時會將內容寫入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文件中有內容,則將第一個key添加到mapped_keys中。不然就默認爲系統的testkey。

4.利用dumpkey.jar爲recovery建立新的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做爲參數,將公鑰內容打印出來,dumpkey.jar的內容以下:

5.更新otacerts.zip

最後會將mapped_keys中的文件寫入otacerts.zip中。若是otakey爲空,則默認爲testkey.x509.pem,若是指定了key的路徑(-d),以及設置了-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進行更新後,生成出來的固件假如與後續的ota包簽名不符,那麼在校驗的時候也是會失敗,因此在生成ota包時,也必須指定相應的公鑰。

ota_from_target_files中有以下的解析:

-k (--package_key) <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).

而且在選項中假如未定義"--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")

當OTA包完成全部的打包工做後,最終會調用到以下方法,代表要對整包進行簽名。

# Sign the whole package to comply with the Android OTA package format.
def SignOutput(temp_zip_name, output_zip_name)

其實現以下:

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將會提示用戶輸入密碼,假如密碼與以前建立公鑰私鑰的密碼不對,則提示錯誤,匹配才能往下進行,並返回更新後{key:password}
  • 根據ota_from_targetfiles中指定的package_key,找到其密碼pw
  • 調用SignFile對OTA包進行整包簽名
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,))

這裏實際進行的命令是:

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包的簽名實際操做以下:

  • 計算出key的個數,這裏因爲公鑰私鑰成對出現,並且指定輸入輸出文件,那麼key的對數即爲在-w以後的個數除以2,再減去1,ota簽名時numKeys爲1.
int numKeys = (args.length - argstart) / 2 - 1;

if ((signWholeFile) && (numKeys > 1)) {//這裏證實了能夠的對數只能爲1
      System.err.println("Only one key may be used with -w.");
      System.exit(2);
}
  • 載入輸入文件和輸出文件參數
String inputFilename = args[(args.length - 2)];
String outputFilename = args[(args.length - 1)];

inputJar = new JarFile(new File(inputFilename), false);
outputFile = new FileOutputStream(outputFilename);
  • 讀取公鑰內容
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結構的publicKey中
    hashes |= getDigestAlgorithm(publicKey[i], minSdkVersion);//計算摘要
  }
} catch (IllegalArgumentException e) {
  System.err.println(e);
  System.exit(1);
}
  • 讀取私鑰內容
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]));
}
  • 進行簽名
signWholeFile(inputJar, firstPublicKeyFile, publicKey[0], privateKey[0], timestamp, minSdkVersion, outputFile);

其實現以下:

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格式核心目錄結束標識是否爲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");
    }
    
    //結尾格式以2字節`signature_staret` ff ff 2字節`total_size`結尾    
    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流的結尾格式
    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. 校驗OTA包

5.1 RecoverySystem校驗OTA包

RecoverySystem中有校驗OTA包的接口

public static void verifyPackage(File packageFile,
            ProgressListener listener,
            File deviceCertsZipFile)

其校驗流程以下:

1.獲取OTA包

final RandomAccessFile raf = new RandomAccessFile(packageFile, "r");

2.校驗OTA包的尾部

raf.seek(fileLen - 6);
byte[] footer = new byte[6];
raf.readFully(footer);
//校驗後6字節中間的兩個字節是否爲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.
//檢查覈心標識是否爲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後四個字節是不是EOCD標識,若是是則報錯
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<X509Certificate> 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選項時更新的。假如沒有指定新的簽名目錄,那麼使用原生的testkey做爲密鑰。因此從校驗能夠看出,至少對ota整包的簽名的公鑰信息必需要與待OTA升級的system/etc/otacerts.zip中的公鑰信息是要一致的,不然將在校驗時出錯。

這個校驗流程與常規的簽名校驗一致,假如加密文件,這裏OTA包,使用了私鑰進行簽名,並在尾部附上公鑰,那麼正常而言,使用該公鑰便可對其進行驗證,可是這裏就多了一個流程是公鑰必需要和須要升級的固件是一致的,至關於CA的一個做用證實該公鑰是有效的,纔會繼續使用公鑰去計算出OTA包中的摘要,而後經過該摘要值與給到的OTA包進行計算的摘要值進行對比,保證該OTA包是沒有通過修改的。

5.對比摘要值

SignerInfo verifyResult = block.verify(signerInfo, new InputStream

這裏的veriry中還實現了一個read方法,應當是對ota包中的內容進行讀取,並與signerinfo的信息進行比較,其中read的內容範圍爲:

// The signature covers all of the OTA package except the
                // archive comment and its 2-byte length.
                long toRead = fileLen - commentSize - 2;

當校驗結果爲null時出錯

if (verifyResult == null) {
                throw new SignatureException("signature digest verification failed");
            
}

5.2 recovery校驗OTA包

recovery的校驗流程與RecoverySystem中相仿。

6. 結論

  • 通常而言,正常編譯OTA包與編譯固件,均是默認使用testkey進行簽名
  • 假如指定了新的簽名目錄,那麼必須保證,待升級的固件中的公鑰信息(包括system以及recovery)都要與升級的OTA包中的公鑰信息等匹配,即便用同一對密鑰對。
相關文章
相關標籤/搜索