最近做的一款产品,由于前期失误使用的是 官方的testkey 签名rom包。所以别人rom可以随意刷我们产品盒子,为了改正这个bug我们使用了新的签名刷自己产品。防止别人盗刷我们盒子。
要用新签名rom包,先要理解android升级原理,
第一步:
当我们手动u盘升级或者ota升级下载好update.zip包后。当我们点击升级后,android framework的代码
./base/core/java/android/os/RecoverySystem.java
中函数会

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

这个函数 会被上层 应用 调用用于检测 rom 包签名
RecoverySystem.verifyPackage(verifyPackage, listener,
new File(“/system/etc/security/otacerts.zip”)); 而otacerts.zip 就是我们的密钥,解压后发现就是 testkey.x509.pem,如果我们rom签名的密钥 用的是 testkey.x509.pem 和testkey.pk8 ,现在用testkey.x509.pem 检测update.zip就能通过。
第二步:
在 recovery 模式升级的时候。我们recovery模式会再一次验证update.zip签名。具体代码在install.cpp 中函数 really_install_package 中

int numKeys;
    RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
    if (loadedKeys == NULL) {
        LOGE("Failed to load keys\n");
        return INSTALL_CORRUPT;
    }
    LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE);

    // Give verification half the progress bar...
    ui->Print("Verifying update package...\n");
    ui->SetProgressType(RecoveryUI::DETERMINATE);
    ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);

    int err;
    err = verify_file(path, loadedKeys, numKeys);
    free(loadedKeys);
    LOGI("verify_file returned %d\n", err);
    if (err != VERIFY_SUCCESS) {
        LOGE("signature verification failed\n");
        return INSTALL_CORRUPT;
    }

首先 获得 public key 公钥,load_keys 返回值就是我们的 key指针地址 ,numKeys
是keys 数量。支持多个key。

load_keys(const char* filename, int* numKeys) {
    RSAPublicKey* out = NULL;
    *numKeys = 0;

    FILE* f = fopen(filename, "r");
    if (f == NULL) {
        LOGE("opening %s: %s\n", filename, strerror(errno));
        goto exit;
    }

    {
        int i;
        bool done = false;
        while (!done) {
            ++*numKeys;
            out = (RSAPublicKey*)realloc(out, *numKeys * sizeof(RSAPublicKey));
            RSAPublicKey* key = out + (*numKeys - 1);

            char start_char;
            if (fscanf(f, " %c", &start_char) != 1) goto exit;
            if (start_char == '{') {
                // a version 1 key has no version specifier.
                key->exponent = 3;
            } else if (start_char == 'v') {
                int version;
                if (fscanf(f, "%d {", &version) != 1) goto exit;
                if (version == 2) {
                    key->exponent = 65537;
                } else {
                    goto exit;
                }
            }

            if (fscanf(f, " %i , 0x%x , { %u",
                       &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
                goto exit;
            }
            if (key->len != RSANUMWORDS) {
                LOGE("key length (%d) does not match expected size\n", key->len);
                goto exit;
            }
            for (i = 1; i < key->len; ++i) {
                if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit;
            }
            if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;
            for (i = 1; i < key->len; ++i) {
                if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;
            }
            fscanf(f, " } } ");

            // if the line ends in a comma, this file has more keys.
            switch (fgetc(f)) {
            case ',':
                // more keys to come.
                break;

            case EOF:
                done = true;
                break;

            default:
                LOGE("unexpected character between keys\n");
                goto exit;
            }

            LOGI("read key e=%d\n", key->exponent);
        }
    }

PUBLIC_KEYS_FILE = /res/keys
在recovery 下面用 busybox cat /res/keys 发现都是数字

{64,0xc926ad21,{1795090719,2141396315,950055447,2581568430,4268923165,1920809988,546586521,3498997798,1776797858,3740060814,1805317999,1429410244,129622599,1422441418,1783893377,1222374759,2563319927,323993566,28517732,609753416,1826472888,215237850,4261642700,4049082591,3228462402,774857746,154822455,2497198897,2758199418,3019015328,2794777644,87251430,2534927978,120774784,571297800,3695899472,2479925187,3811625450,3401832990,2394869647,3267246207,950095497,555058928,414729973,1136544882,3044590084,465547824,4058146728,2731796054,1689838846,3890756939,1048029507,895090649,247140249,178744550,3547885223,3165179243,109881576,3944604415,1044303212,3772373029,2985150306,3737520932,3599964420},{3437017481,3784475129,2800224972,3086222688,251333580,2131931323,512774938,325948880,2657486437,2102694287,3820568226,792812816,1026422502,2053275343,2800889200,3113586810,165549746,4273519969,4065247892,1902789247,772932719,3941848426,3652744109,216871947,3164400649,1942378755,3996765851,1055777370,964047799,629391717,2232744317,3910558992,191868569,2758883837,3682816752,2997714732,2702529250,3570700455,3776873832,3924067546,3555689545,2758825434,1323144535,61311905,1997411085,376844204,213777604,4077323584,9135381,1625809335,2804742137,2952293945,1117190829,4237312782,1825108855,3013147971,1111251351,2568837572,1684324211,2520978805,367251975,810756730,2353784344,1175080310}}

可能别的keys值和这个不一样。这没关系。
这个keys怎么得来的呢??
在 源码目录build/target/product/security下执行命令
java -jar ../../../../out/host/linux-x86/framework/dumpkey.jar testkey.x509.pem > keys

具体文件 在 build/core/Makefile 中

$(RECOVERY_INSTALL_OTA_KEYS): $(OTA_PUBLIC_KEYS) $(DUMPKEY_JAR) $(extra_keys)
    @echo "DumpPublicKey: $@ <= $(PRIVATE_OTA_PUBLIC_KEYS) $(extra_keys)"
    @rm -rf $@
    @mkdir -p $(dir $@)
    java -jar $(DUMPKEY_JAR) $(PRIVATE_OTA_PUBLIC_KEYS) $(extra_keys) > $@

上面源码就是 build/core/Makefile

RECOVERY_INSTALL_OTA_KEYS 就是 /res/keys
OTA_PUBLIC_KEYS =  $(SRC_TARGET_DIR)/product/security/testkey.x509.pem
 DUMPKEY_JAR := $(HOST_OUT_JAVA_LIBRARIES)/dumpkey.jar

load_keys获得 RSAPublicKey之后,我们通过 verify_file(path, loadedKeys, numKeys); 验证 update.zip签名。

验证通过就升级update.zip,不通过就失败。

用新的签名当然先新建新的签名:
文件 development/tools/make_key 就是用于做新签名的工具
执行 命令

./make_key onekey  '/C=CN/ST=ShangHai/L=ShangHai/O=PPTV/OU=Department/CN=Your Name/emailAddress=YourE-mailAddress'
key的名字很好理解,就是前面提到的4中类型的key,公司信息的参数比较多,它们的含义如下:

C   --->  Country Name (2 letter code)
ST  --->  State or Province Name (full name)
L   --->  Locality Name (eg, city)
O   --->  Organization Name (eg, company)
OU  --->  Organizational Unit Name (eg, section)
CN  --->  Common Name (eg, your name or your server’s hostname)
emailAddress --->  Contact email address

执行上面命令提示你输入密码。输入你的密码后就会在当前目录下生成 onekey.x509.pem 和 onekey.pk8 就是新签名 密钥。
原来签名 用的是 testkey签名 update.zip,现在我们要用 onekey 签名。所有我们要有一个过度updat.zip包,update.zip 签名是用 testkey 签名。但是这个update.zip 包含的签名文件(otacert.zip 和 recovery 模式下的res/keys)都是 onekey 的。
打开 build/core/Makefile

DEFAULT_KEY_CERT_PAIR := $(DEFAULT_SYSTEM_DEV_CERTIFICATE)

DEFAULT_KEY_CERT_PAIR 是用于签名 update.zip 文件。

java -Xmx1536m -jar out/host/linux-x86/framework/signapk.jar -w build/target/product/security/
testkey.x509.pem build/target/product/security/testkey.pk8 /tmp/tmpg95cOx out/target/product/g18ref/g18ref-ota-
20150508.zip

就是上面的testkey.pk8 和testkey.x509.pem
我们改为

DEFAULT_KEY_CERT_PAIR := $(DEFAULT_SYSTEM_DEV_CERTIFICATE) 
DEFAULT_KEY_CERT_PAIR := $(SRC_TARGET_DIR)/product/security/onetkey $(DEFAULT_KEY_CERT_PAIR)

上面展开 makefile 就是 otakey 和 testkey

$(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(firstword $(DEFAULT_KEY_CERT_PAIR))

上面 签名时候选择 第一个签名 onetkey

$(TARGET_OUT_ETC)/security/otacerts.zip: $(addsuffix .x509.pem,$(DEFAULT_KEY_CERT_PAIR))
 $(hide) rm -f $@
 $(hide) mkdir -p $(dir $@)
 $(hide) zip -qj $@ $<

将最后一句改为

$(hide) zip -qj $@ $^

上面用于 生成 otacerts.zip,我们让 otacerts.zip 包含 testkey 和 onekey 新旧 update.zip 在 都能验证通过。

ifeq ($(BUILD_SECURE),true)
    OTA_PUBLIC_KEYS := device/*/$(TARGET_DEVICE)/releasekey.x509.pem
else
    OTA_PUBLIC_KEYS := $(SRC_TARGET_DIR)/product/security/testkey.x509.pem
endif

后面添加这句

OTA_PUBLIC_KEYS := $(SRC_TARGET_DIR)/product/security/otakey.x509.pem

这句话用于 生成 recovery 模式下 /res/keys

cp $(RECOVERY_INSTALL_OTA_KEYS) $(TARGET_RECOVERY_ROOT_OUT)/res/keys 
添加后面一句
cp $(TARGET_OUT_ETC)/security/otacerts.zip $(TARGET_RECOVERY_ROOT_OUT)/res/otacerts.zip

为什么加这句。比如我现在版本(老的签名)用新的签名updtae.zip包(不是过度包)升级之后(otacert.zip包含testkey和 onekey)。我又想回到这个老版本,升级后otacerts.zip 只包含testkey,这时候我再想升级新的update.zip包(新签名包),如果不加这句就会还要用过度包。这个 otacerts.zip 会被在recovery 使用。

在 文件 recovery.cpp 中 添加

static  char *INSTALL_RECOVRY_SH = "/system/etc"; 
static  char *SOURCE_OTACERTZIP = "/res/otacerts.zip"; 
static  char *DEST_OTACERTZIP = "/system/etc/security/otacerts.zip"; 
 int old_updatezip_flag = 0;

添加函数 用于 将 res目录下的 /res/otacerts.zip 拷贝到 /system/etc/security/otacerts.zip

void   
   copy_otacert_file(const char* source, const char* destination) { 
       FILE *des = fopen(destination, "w");
       FILE *tmplog = fopen(source, "r"); 
       if (tmplog != NULL) {  
           fseek(tmplog, 0, SEEK_SET);     
           char buf[4096]; 456 
           while(fread(buf,1,sizeof(buf),tmplog)){
               fwrite(buf,sizeof(buf),1,des);    
           } 
       } 
       fclose(tmplog); 
       fclose(des); 
   }
finish_recovery(send_intent); 
       if(old_updatezip_flag==1)
       { 
           if (ensure_path_mounted(INSTALL_RECOVRY_SH) == 0) { 
               unlink("/system/etc/install-recovery.sh");   
               copy_otacert_file(SOURCE_OTACERTZIP,DEST_OTACERTZIP); 
           } 
           ensure_path_unmounted(INSTALL_RECOVRY_SH); 
       }

如果是 在 新签名 recovery 下升级 旧签名 update.zip ,需要在升级完成后 删除 install-recovery.sh ,不然 在正常启动后,install-recovery.sh 会把 当面新签名recovery 换成老的recovery。
copy_otacert_file(SOURCE_OTACERTZIP,DEST_OTACERTZIP); 替换
旧的 otacerts.zip
if(old_updatezip_flag==1) 表示 当前要升级 旧签名的update.zip包。这个值确定在文件verifier.cpp中。

在 recovery 模式下。我们将我们之前 发布的 rom 的sha散列值 都计算 好保存到我们recovery 中。

head -c -1740 update.zip | sha1sum

用上面函数计算,去掉 尾部 1740个字节这些都是签名文件 自己加上去的。

size_t comment_size = footer[4] + (footer[5] << 8);
    size_t signature_start = footer[0] + (footer[1] << 8);
    LOGI("comment is %d bytes; signature %d bytes from end\n",

上面函数打印出signature = 1740个字节 ,所以计算 sha值时候去掉 1740

extern int old_updatezip_flag;  声明外部变量

char *known_sha1s[] = {
            "01c041ee4803755cc4bc00e108e0a613dcc782ab",
            "a23f051c805b586608ccd63d0257ad867f577bdf",
            "5acb8d649a0416ae1730e2a3b45aafb1727bb9be",
            "4bfe92f6f2bafb20979d881b773933dcc438f88b",
            "82fb1103c4e8d281e891bae12d26283332cb74ac",
            "6349f6ec3794e799b52c87eee674dc3ad2435c79",
            "dc2a66de723aa1889eaa6b7cef2fb05d8d9a7590",
            "9406448609c52c0e8109afbfb77110f838f41c4e",
            "a589842eb4ab97e45dc1d68041e77dd72a71a63c",
            "b6e27016ff3eb502c634b753427d9f179e15f2ff",
            "a6277d7ef65dda2a061aa7cd1620d82e787f9de4",
            "903f681c6c4e177007b1653128f61b19d247444f",
            "3715d801b92a5f1edbbcc819b1f57f2def1c85de",
            "a694007371fd0e11daef3a72b8fefe071ca61a28",
            "a623c066541f0ea8476e8b39ccbd4dcc7dac96ec",
            "9affed9d8ddf7e0748b4698080924f0e03aee405",
            "6514aba7766a47a8c42cd72ca015879be6480742",
            "bf7f2cfb83036679f322e3799c160ad05f8cf57d",
            "96987b650e13add4afc91da7e41c0dba3edaf389",
            "NULL"};
const uint8_t* sha1 = SHA_final(&ctx);
    for (i = 0; i < numKeys; ++i) {
        // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that
        // the signing tool appends after the signature itself.
        if (RSA_verify(pKeys+i, eocd + eocd_size - 6 - RSANUMBYTES,
                       RSANUMBYTES, sha1)) {
            LOGI("whole-file signature verified against key %d\n", i);
            fclose(f);
            free(eocd);
            return VERIFY_SUCCESS;
        }
    }
    下面都是新加的代码 
    /*verify old public rom */
    char tmp_sha[42] = {0};
    char const * hex = "0123456789ABCDEF";
    for(i=0;i<20;i++)
    {   
        tmp_sha[i*2] = hex[sha1[i] >> 4];
        tmp_sha[i*2 + 1] = hex[sha1[i] & 0x0f];
    }
    将 uint8_t* 16进制 字符转成字符串 
    char **p ;
    p= known_sha1s;
    while(strcasecmp(*p,"NULL")!=0)
    {
        if(strcasecmp(*p,tmp_sha)==0)
        {
            old_updatezip_flag =1;
            这个值等于1 表明是 旧签名的发布update包,我们在升级旧rom包 
            fclose(f);  
            return VERIFY_SUCCESS;
        }
        p++;
    }
 先校验是不是 旧签名 发布 rom 包
        /*校验 过度 rom*/ 如果是过度 rom  我们 查找某个字符是否相等 就让过度包通过校验可以升级。
    if (fseek(f, -comment_size, SEEK_END) != 0) {
        LOGE("failed to seek in %s (%s)\n", path, strerror(errno));
        fclose(f);
        return VERIFY_FAILURE;
    }
    unsigned char sig[1];
    if (fread(sig, 1, 1, f) != 1) {
        LOGE("failed to read footer from %s (%s)\n", path, strerror(errno));
        fclose(f);
        return VERIFY_FAILURE;
    }
    if(sig[0]==83)
    {
        fclose(f);
        return VERIFY_SUCCESS;
    }

上面思路是 先用新的签名 校验 rom 包 。不能通过再比较rom 包sha 散列值。不通过再检验是不是过度包。
如果编译不通过 可能错误原因 是
verifier_test.cpp 中少 old_updatezip_flag
在文件中加入 int old_updatezip_flag;
整个 防刷机原理是 过度包刷完之后,
正常模式下 可以让old 签名包 和别人家的包和我们新签名包都通过验证。因为我们 otacerts.zip 包含 新旧 key,但是 我们返回旧key 签名 rom public 包。升级完成后 我们删除 install-recovert.sh ,并将 recovery 下的otacerts.zip拷贝到 system 下面。这样旧 public rom 和 新签名包可以来回 刷。但是 别家的却不能再刷通过 我们盒子。因为我们recovery 模式下面没有包含他们 sha值 known_sha1s 。有关过度包我们在验证通过我们做了 校验 update.zip 1740 字节某个字符是否相等