七、recovery ota升级包签名生成/校验

最近一直有两个疑问 升级包签名的规则和签名文件具体的位置,所以大概看了下签名流程并整理出来

大概理解了下 1.如何签名 2.如何校验

 

一.相关整理

首先要大概知道的两个内容

1.CMS数字签名

参考:https://www.ibm.com/developerworks/cn/java/j-lo-cms-ticketbasesso/

大概理解为

签名阶段:取数据的hash 使用私钥进行加密放入签名部分, 签名部分+数据部分 组合为新的zip

校验阶段:取出数据的hash,使用公钥对签名部分的加密hash进行解密,解密后的hash和数据的hash进行对比

2.zip文件的结构

参考:http://blog.sina.com.cn/s/blog_c496a6310102wje4.html

这里主要理解以下其中的End of central directory record

 

二.生成签名

做升级包的流程还是从ota_from_target_files ,这部分略过,直接从签名开始分析,传入的什么参数

最后的签名命令为:

java -Djava.library.path=out/host/linux-x86/lib64

-jar out/host/linux-x86/framework/signapk.jar

-w device/mediatek/security/releasekey.x509.pem ./device/mediatek/security/releasekey.pk8

update.zip  update_sign.zip 2>&1 | tee sign.log

 

所以签名的流程从sign.jar开始,源码位于build/make/tools/signapk/src/com/android/signapk/SignApk.java

jar文件也是从main方法开始 开始上菜,第一道小炒肉

1.main方法

几句话总结

1.解析传入的参数 根据-w判断是否为签名升级包

2.获取输入和输出

3.获取公钥 转化为x509证书格式 readPublicKey方法

4.定义升级内文件将使用的时间戳  (这个其实是为了签名APK时 有点用处,官方文档中说可以减少patch的生成)

5.获取私钥  readPrivateKey

6.定义签名算法的类型 getDigestAlgorithmForOta

7.将以上参数传入签名升级包的方法signWholeFile

    //从main方法开始
    public static void main(String[] args) {
        if (args.length < 4) usage();
        //输出一波参数信息
        //arg = -w
        //arg = device/mediatek/security/releasekey.x509.pem
        //arg = ./device/mediatek/security/releasekey.pk8
        //arg = update.zip
        //arg = update_sign.zip
        for(String arg : args){
          System.out.println("arg = " + arg);
        }
        // Install Conscrypt as the highest-priority provider. Its crypto primitives are faster than
        // the standard or Bouncy Castle ones.
        Security.insertProviderAt(new OpenSSLProvider(), 1);
        // Install Bouncy Castle (as the lowest-priority provider) because Conscrypt does not offer
        // DSA which may still be needed.
        // TODO: Stop installing Bouncy Castle provider once DSA is no longer needed.
        Security.addProvider(new BouncyCastleProvider());

        boolean signWholeFile = false;
        String providerClass = null;
        int alignment = 4;
        Integer minSdkVersionOverride = null;
        boolean signUsingApkSignatureSchemeV2 = true;
        //1.解析传入的参数 根据-w判断是否为签名升级包
        int argstart = 0;
        while (argstart < args.length && args[argstart].startsWith("-")) {
            if ("-w".equals(args[argstart])) {
                signWholeFile = true;
                ++argstart;
            } else if ("-providerClass".equals(args[argstart])) {
                if (argstart + 1 >= args.length) {
                    usage();
                }
                providerClass = args[++argstart];
                ++argstart;
            } else if ("-a".equals(args[argstart])) {
                alignment = Integer.parseInt(args[++argstart]);
                ++argstart;
            } else if ("--min-sdk-version".equals(args[argstart])) {
                String minSdkVersionString = args[++argstart];
                try {
                    minSdkVersionOverride = Integer.parseInt(minSdkVersionString);
                } catch (NumberFormatException e) {
                    throw new IllegalArgumentException(
                            "--min-sdk-version must be a decimal number: " + minSdkVersionString);
                }
                ++argstart;
            } else if ("--disable-v2".equals(args[argstart])) {
                signUsingApkSignatureSchemeV2 = false;
                ++argstart;
            } else {
                usage();
            }
        }
        //args.length = 5
        //argstart = 1
        System.out.println("args.length = " + args.length);
        System.out.println("argstart = " + argstart);
        //如果去掉开头的-w余数等于2 说明参数不对
        if ((args.length - argstart) % 2 == 1) usage();
        //这里numKeys其实是1
        int numKeys = ((args.length - argstart) / 2) - 1;
        //这里的打印也能看出来,当签名升级包的时候 签名只有一个
        if (signWholeFile && numKeys > 1) {
            System.err.println("Only one key may be used with -w.");
            System.exit(2);
        }

        loadProviderIfNecessary(providerClass);
        //2.获取输入和输出
        //获取输入文件 和输出文件的名称 倒数第一个参数和倒数第二个
        String inputFilename = args[args.length-2];
        String outputFilename = args[args.length-1];

        JarFile inputJar = null;
        //定义文件流输出文件
        FileOutputStream outputFile = null;
        
        try {
            //3.获取公钥文件releasekey.x509.pem
            File firstPublicKeyFile = new File(args[argstart+0]);
            //创建x509证书对象
            X509Certificate[] publicKey = new X509Certificate[numKeys];
            try {
                for (int i = 0; i < numKeys; ++i) {
                    int argNum = argstart + i*2;
                    //将公钥转换为x509证书格式
                    publicKey[i] = readPublicKey(new File(args[argNum]));
                    System.out.println("publicKey" + "[" + i + "]" + publicKey[i]);
                }
            } catch (IllegalArgumentException e) {
                System.err.println(e);
                System.exit(1);
            }

            // Set all ZIP file timestamps to Jan 1 2009 00:00:00.
            // 4.创建了一个时间戳 签名之后 文件内容时间为2009 00:00:00.
            long timestamp = 1230768000000L;
            // The Java ZipEntry API we're using converts milliseconds since epoch into MS-DOS
            // timestamp using the current timezone. We thus adjust the milliseconds since epoch
            // value to end up with MS-DOS timestamp of Jan 1 2009 00:00:00.
            timestamp -= TimeZone.getDefault().getOffset(timestamp);
            //5.获取私钥releasekey.pk8
            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]));
                System.out.println("privateKey" + "[" + i + "]" + privateKey[i]);
            }
            //将输入文件转换为了jar文件
            inputJar = new JarFile(new File(inputFilename), false);  // Don't verify.

            outputFile = new FileOutputStream(outputFilename);

            // NOTE: Signing currently recompresses any compressed entries using Deflate (default
            // compression level for OTA update files and maximum compession level for APKs).
            // 如果signWholeFile为true 使用signWholeFile方法
            if (signWholeFile) {
                //6.定义签名算法的类型 
                int digestAlgorithm = getDigestAlgorithmForOta(publicKey[0]);
                //digestAlgorithm = 1
                System.out.println("digestAlgorithm = " + digestAlgorithm);
                // inputJar输入文件,也就是原始的升级包
                // firstPublicKeyFile 公钥文件
                // publicKey[0]x509证书格式
                // privateKey[0] 解析后的PrivateKey私钥
                // digestAlgorithm 签名算法的类型
                // outputFile 输出文件
                signWholeFile(inputJar, firstPublicKeyFile,
                        publicKey[0], privateKey[0], digestAlgorithm,
                        timestamp,
                        outputFile);
            } else {
            //后面的不用看了,签名APK用的
            ......
            ......

 

其中用到几个方法,大致过下

1.1 readPublicKey

    private static X509Certificate readPublicKey(File file)
        throws IOException, GeneralSecurityException {
        FileInputStream input = new FileInputStream(file);
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            return (X509Certificate) cf.generateCertificate(input);
        } finally {
            input.close();
        }
    }

1.2 readPrivateKey

    /** Read a PKCS#8 format private key.读取PKCS#8格式的私钥 */
    private static PrivateKey readPrivateKey(File file)
        throws IOException, GeneralSecurityException {
        //创建数据输入流
        DataInputStream input = new DataInputStream(new FileInputStream(file));
        try {
            //一次性把长度都读完
            byte[] bytes = new byte[(int) file.length()];
            input.read(bytes);

            /* Check to see if this is in an EncryptedPrivateKeyInfo structure. 检查这是否在EncryptedPrivateKeyInfo结构中*/
            PKCS8EncodedKeySpec spec = decryptPrivateKey(bytes, file);
            if (spec == null) {
                spec = new PKCS8EncodedKeySpec(bytes);
            }

            /*
             * Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
             * OID and use that to construct a KeyFactory.
             * 现在,它处于PKCS#8 PrivateKeyInfo结构中。 阅读其算法OID并将其用于构造KeyFactory。
             */
            PrivateKeyInfo pki;
            try (ASN1InputStream bIn =
                    new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()))) {
                pki = PrivateKeyInfo.getInstance(bIn.readObject());
            }
            String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
            System.out.println("algOid = " + algOid);
            return KeyFactory.getInstance(algOid).generatePrivate(spec);
        } finally {
            input.close();
        }
    }
    /**
     * Decrypt an encrypted PKCS#8 format private key.
     * 解密加密的PKCS#8格式的私钥。
     * Based on ghstark's post on Aug 6, 2006 at
     * http://forums.sun.com/thread.jspa?threadID=758133&messageID=4330949
     *
     * @param encryptedPrivateKey The raw data of the private key 私钥的数据
     * @param keyFile The file containing the private key  私钥文件
     */
    private static PKCS8EncodedKeySpec decryptPrivateKey(byte[] encryptedPrivateKey, File keyFile)
        throws GeneralSecurityException {
        EncryptedPrivateKeyInfo epkInfo;
        try {
            epkInfo = new EncryptedPrivateKeyInfo(encryptedPrivateKey);
        } catch (IOException ex) {
            // Probably not an encrypted key.
            return null;
        }

        char[] password = readPassword(keyFile).toCharArray();

        SecretKeyFactory skFactory = SecretKeyFactory.getInstance(epkInfo.getAlgName());
        Key key = skFactory.generateSecret(new PBEKeySpec(password));

        Cipher cipher = Cipher.getInstance(epkInfo.getAlgName());
        cipher.init(Cipher.DECRYPT_MODE, key, epkInfo.getAlgParameters());

        try {
            return epkInfo.getKeySpec(cipher);
        } catch (InvalidKeySpecException ex) {
            System.err.println("signapk: Password for " + keyFile + " may be bad.");
            throw ex;
        }
    }

1.3 getDigestAlgorithmForOta

    /**
     * Returns the digest algorithm ID (one of {@code USE_SHA1} or {@code USE_SHA256}) to be used
     * for signing an OTA update package using the private key corresponding to the provided
     * certificate.从公钥中读取是sha1算法 还是sha256算法,用于使用与提供的证书相对应的私钥来签署OTA更新包。
     */
    private static int getDigestAlgorithmForOta(X509Certificate cert) {
        String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
        if ("SHA1WITHRSA".equals(sigAlg) || "MD5WITHRSA".equals(sigAlg)) {
            // see "HISTORICAL NOTE" above.
            return USE_SHA1;
        } else if (sigAlg.startsWith("SHA256WITH")) {
            return USE_SHA256;
        } else {
            throw new IllegalArgumentException("unsupported signature algorithm \"" + sigAlg +
                                               "\" in cert [" + cert.getSubjectDN());
        }
    }

2.signWholeFile

大致流程总结

1.创建CMSSigner对象 将参数传入其构造函数

2.创建字节数组输出流temp

3.写入了一个byte类型signed by SignApk字符串到comment 用于一些工具的查看

4.拷贝输入流到输出流,对升级包除去comment length 和 comment 内容进行cms数字签名并将签名信息写入temp

5.获取输出流末尾 因为现在输出流中其实没有comment 所有可以通过这种方式检验是不是真的没有

6.获取comment的总长度total_size,加上6用于存放comment 长度和 signture start长度

7.获取signature_start的长度 并在加的6个字节中写入signature_start 0xff 0xff total_size

8.将comment 长度 total_size 和 comment temp数据写入到输出流中

 

还是把传入的参数放上来
// inputJar输入文件,也就是原始的升级包
// firstPublicKeyFile 公钥文件
// publicKey[0]x509证书格式
// privateKey[0] 解析后的PrivateKey私钥
// digestAlgorithm 签名算法的类型
// outputFile 输出文件  
    private static void signWholeFile(JarFile inputJar, File publicKeyFile,
                                      X509Certificate publicKey, PrivateKey privateKey,
                                      int hash, long timestamp,
                                      OutputStream outputStream) throws Exception {
        System.out.println("signWholeFile");
        //1.创建CMSSigner对象
        CMSSigner cmsOut = new CMSSigner(inputJar, publicKeyFile,
                publicKey, privateKey, hash, timestamp, outputStream);
        //2.创建字节数组输出流
        ByteArrayOutputStream temp = new ByteArrayOutputStream();

        // put a readable message and a null char at the start of the
        // archive comment, so that tools that display the comment
        // (hopefully) show something sensible.
        // TODO: anything more useful we can put in this message?
        // 在存档注释的开头放置一条可读消息和一个空字符,以便显示注释的工具显示出一些有意义的信息。
        //3. 写入了一个byte类型signed by SignApk 到comment 用于一些工具的查看
        byte[] message = "signed by SignApk".getBytes(StandardCharsets.UTF_8);
        temp.write(message);
        // 写入一个空字符
        temp.write(0);
        System.out.println("message = " + message.length);
        //4.拷贝输入流到输出流,对升级包除去comment length 和 comment 内容进行cms数字签名
        //并将签名信息写入temp
        cmsOut.writeSignatureBlock(temp);
        //5.获取末尾???? 因为现在输出流中其实没有comment 所有可以通过这种方式检验是不是真的没有
        byte[] zipData = cmsOut.getSigner().getTail();
        System.out.println("zipData = " + zipData);
        System.out.println("zipData length = " + zipData.length);
        // For a zip with no archive comment, the
        // end-of-central-directory record will be 22 bytes long, so
        // we expect to find the EOCD marker 22 bytes from the end.
        // 如果压缩包没有存档注释,确认末尾有没有22字节的EOCD marker
        if (zipData[zipData.length-22] != 0x50 ||
            zipData[zipData.length-21] != 0x4b ||
            zipData[zipData.length-20] != 0x05 ||
            zipData[zipData.length-19] != 0x06) {
            throw new IllegalArgumentException("zip data already has an archive comment");
        }
        //6.获取comment的总长度total_size,加上6用于存放comment 长度和 signture start长度
        System.out.println("temp.size() = " + temp.size());
        System.out.println("signature_length = " + (temp.size() - message.length - 1));
        int total_size = temp.size() + 6;
        //确认下签名的长度有没有问题
        if (total_size > 0xffff) {
            throw new IllegalArgumentException("signature is too big for ZIP file comment");
        }
        // signature starts this many bytes from the end of the file签名从文件末尾开始这么多字节
        // 7.获取signature_start的长度 并在加的6个字节中写入signature_start 0xff 0xff total_size
        // 这里相当于是签名的长度加6 signture_start和total_size长度的差值是固定的 就是18
        // 有个疑问点,signature的长度不就是之前算的temp的长度- message.length - 1 干嘛还搞个start
        int signature_start = total_size - message.length - 1;
        //这里相当于多加了6个字节 两个字节标识出开始签名的长度,
        temp.write(signature_start & 0xff);
        temp.write((signature_start >> 8) & 0xff);
        // Why the 0xff bytes?  In a zip file with no archive comment,
        // bytes [-6:-2] of the file are the little-endian offset from
        // the start of the file to the central directory.  So for the
        // two high bytes to be 0xff 0xff, the archive would have to
        // be nearly 4GB in size.  So it's unlikely that a real
        // commentless archive would have 0xffs here, and lets us tell
        // an old signed archive from a new one.
        // 两个字节写入的0xff
        temp.write(0xff);
        temp.write(0xff);
        //两个字节标识出总长度
        temp.write(total_size & 0xff);
        temp.write((total_size >> 8) & 0xff);
        temp.flush();
        // Signature verification checks that the EOCD header is the
        // last such sequence in the file (to avoid minzip finding a
        // fake EOCD appended after the signature in its scan).  The
        // odds of producing this sequence by chance are very low, but
        // let's catch it here if it does.
        // 签名验证检查EOCD标头是否是文件中的最后一个这样的序列(以避免minzip在扫描签名后附加伪造的EOCD)
        // 校验comment中是不是存在eocd,
        byte[] b = temp.toByteArray();
        System.out.println("b.length = " + b.length);
        for (int i = 0; i < b.length-3; ++i) {
            if (b[i] == 0x50 && b[i+1] == 0x4b && b[i+2] == 0x05 && b[i+3] == 0x06) {
                throw new IllegalArgumentException("found spurious EOCD header at " + i);
            }
        }
        //8.将comment 长度 total_size 和 comment temp数据写入到输出流中
        outputStream.write(total_size & 0xff);
        outputStream.write((total_size >> 8) & 0xff);
        //数据写到"输出流out"中
        temp.writeTo(outputStream);
    }

以下对其中的一些流程单独解析

2.1 cmsOut.writeSignatureBlock(temp);

拷贝输入流到输出流,对升级包除去comment length 和 comment 内容进行cms数字签名,并将签名信息写入temp

        public void writeSignatureBlock(ByteArrayOutputStream temp)
            throws IOException,
                   CertificateEncodingException,
                   OperatorCreationException,
                   CMSException {
            SignApk.writeSignatureBlock(this, publicKey, privateKey, hash, temp);
        }
    /** Sign data and write the digital signature to 'out'.签名数据并将数字签名写入输出 */
    private static void writeSignatureBlock(
        CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey, int hash,
        OutputStream out)
        throws IOException,
               CertificateEncodingException,
               OperatorCreationException,
               CMSException {
        System.out.println("writeSignatureBlock");
        //生成cms数字签名 这是固定的方法
        //创建X509Certificate类型的集合 长度为1
        ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>(1);
        // 将公钥添加入集合
        certList.add(publicKey);
        //创建JcaCertStore 存放了签名集合
        JcaCertStore certs = new JcaCertStore(certList);
        System.out.println("CMSSignedDataGenerator gen");
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        //这里其实是 new JcaContentSignerBuilder("SHA1withRSA").build(privateKey);
        ContentSigner signer =
                new JcaContentSignerBuilder(
                        getJcaSignatureAlgorithmForOta(publicKey, hash))
                        .build(privateKey);
        System.out.println("gen.addSignerInfoGenerator");
        gen.addSignerInfoGenerator(
            new JcaSignerInfoGeneratorBuilder(
                new JcaDigestCalculatorProviderBuilder()
                .build())
            .setDirectSignature(true)
            .build(signer, publicKey));
        System.out.println("gen.addCertificates");
        gen.addCertificates(certs);
        System.out.println("CMSSignedData sigData = gen.generate(data, false)");
        CMSSignedData sigData = gen.generate(data, false);
        //将数字签名写入到输出流中
        try (ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded())) {
            System.out.println("try ASN1InputStream asn1");
            //sigData.getEncoded()是生成的数字签名 [B@326de728
            System.out.println("sigData.getEncoded() = " + sigData.getEncoded());
            DEROutputStream dos = new DEROutputStream(out);
            dos.writeObject(asn1.readObject());
        }
    }

这个方法就是固定生成CMS数字签名的流程 单看好像没啥问题,但是没看出来是怎么拿到需要签名的数据的,所以加了很多的log,大致流程如下

signWholeFile

message = 17

writeSignatureBlock

CMSSignedDataGenerator gen

keyAlgorithm = RSA

gen.addSignerInfoGenerator

gen.addCertificates

CMSSignedData sigData = gen.generate(data, false)

CMSSigner write()

构造函数 data.length = 0

 

一目了然,执行gen.generate(data, false)拿到了拷贝后的输出数据和数字签名数据

write方法大致流程如下

1.实例化signer

2.创建输出流

3.调用copyFiles 将输入流中的文件按顺序拷贝到输入流中 去除去除otacert文件

4.调用addOtacert 创建otacert文件将公钥内容拷贝进去

5.以上两个步骤拷贝数据过程中会执行WholeFileSignerOutputStream的write方法生成两份输出

两份包含去除了comment size 和data的升级包数据 代码了里在写入升级包最后数据的时候-2其实是eocd record中

存储comment size的两个字节 这两份一份儿用于生成数字签名,一份儿作为输出

6.同时会将完整的最后数据写入到footer 用于校验升级包是否已经包含了comment

2.2 CMSSigner  write

        @Override
        public void write(OutputStream out) throws IOException {
            System.out.println("write()");
            try {
                //实例化signer对象
                signer = new WholeFileSignerOutputStream(out, outputStream);
                JarOutputStream outputJar = new JarOutputStream(signer);

                //拷贝文件 添加时间戳 去除otacert文件
                copyFiles(inputJar, STRIP_PATTERN, null, outputJar, timestamp, 0);
                // 添加otacert文件,将公钥内容拷贝进去
                addOtacert(outputJar, publicKeyFile, timestamp);
                //以上写入outputjar过程中 signer也会执行写入 notifyclosing是将close标志位改为true
                signer.notifyClosing();
                //关闭jar输出流
                outputJar.close();
                signer.finish();
            }
            catch (Exception e) {
                throw new IOException(e);
            }
        }

2.3 CMSSigner copyFiles

    /**
     * Copy all JAR entries from input to output. We set the modification times in the output to a
     * fixed time, so as to reduce variation in the output file and make incremental OTAs more
     * efficient.
     * 拷贝了输入流的文件 去除了otacert,加了一个时间戳,同时压缩包里非压缩和压缩文件分开处理
     */
    private static void copyFiles(
            JarFile in,
            Pattern ignoredFilenamePattern,
            ApkSignerEngine apkSigner,
            JarOutputStream out,
            long timestamp,
            int defaultAlignment) throws IOException {
        System.out.println("copyFiles");
        //定义4096字节的buffer
        byte[] buffer = new byte[4096];
        int num;
        //定义string集合 拿到输入文件中所有的文件内容,除了otacert
        ArrayList<String> names = new ArrayList<String>();
        //遍历出输入文件中所有的文件,包括文件夹
        for (Enumeration<JarEntry> e = in.entries(); e.hasMoreElements();) {
            //entry就是读取的每个文件了,
            JarEntry entry = e.nextElement();
            //如果是文件夹,跳出循环 执行下一个
            if (entry.isDirectory()) {
                System.out.println("entry.isDirectory() entry = " + entry.getName());
                continue;
            }
            String entryName = entry.getName();
            System.out.println("entryName = " + entryName);
            //对比有没有otacert 如果有的话 跳出循环,也就是说跑完循环之后的集合name没有otacert这个文件
            if ((ignoredFilenamePattern != null)
                    && (ignoredFilenamePattern.matcher(entryName).matches())) {
                System.out.println("ignoredFilenamePattern entryName = " + entryName);
                continue;
            }
            //将文件添加入list
            names.add(entryName);
        }
    System.out.println("list names = " + names);
        //对文件名称的集合进行排序
        Collections.sort(names);
        System.out.println("after sort...");
        System.out.println("list names = " + names);

        boolean firstEntry = true;
        long offset = 0L;

        // We do the copy in two passes -- first copying all the
        // entries that are STORED, then copying all the entries that
        // have any other compression flag (which in practice means
        // DEFLATED).  This groups all the stored entries together at
        // the start of the file and makes it easier to do alignment
        // on them (since only stored entries are aligned).
        // 拷贝分为了两部,一部分是升级的镜像非压缩,另一部分默认压缩
        // 创建string类型的集合,
        List<String> remainingNames = new ArrayList<>(names.size());
        for (String name : names) {
            //根据名称返回条目
            JarEntry inEntry = in.getJarEntry(name);
            //如果类型不是JarEntry.STORED
            if (inEntry.getMethod() != JarEntry.STORED) {
                // Defer outputting this entry until we're ready to output compressed entries.
                // 先把压缩的文件放入remainingNames集合中
                remainingNames.add(name);
                continue;
            }
            //这里apkSigner传入的是null,直接返回true,啥也没干
            if (!shouldOutputApkEntry(apkSigner, in, inEntry, buffer)) {
                continue;
            }

            // Preserve the STORED method of the input entry.保留输入条目的STORED方法
            // 根据现有的条目创建新的条目
            JarEntry outEntry = new JarEntry(inEntry);
            //在条目中写入时间戳
            outEntry.setTime(timestamp);
            // Discard comment and extra fields of this entry to
            // simplify alignment logic below and for consistency with
            // how compressed entries are handled later.
            // 舍弃该条目的注释和多余字段以简化下面的对齐逻辑,并与以后处理压缩条目的方式保持一致。
            outEntry.setComment(null);
            outEntry.setExtra(null);
            //defaultAlignment=0 这里对于升级包来说 其实alignment就是0
            int alignment = getStoredEntryDataAlignment(name, defaultAlignment);
            // Alignment of the entry's data is achieved by adding a data block to the entry's Local
            // File Header extra field. The data block contains information about the alignment
            // value and the necessary padding bytes (0x00) to achieve the alignment.  This works
            // because the entry's data will be located immediately after the extra field.
            // See ZIP APPNOTE.txt section "4.5 Extensible data fields" for details about the format
            // of the extra field.

            // 'offset' is the offset into the file at which we expect the entry's data to begin.
            // This is the value we need to make a multiple of 'alignment'.
            //  public static final int LOCHDR=30
            offset += JarFile.LOCHDR + outEntry.getName().length();
            if (firstEntry) {
                // The first entry in a jar file has an extra field of four bytes that you can't get
                // rid of; any extra data you specify in the JarEntry is appended to these forced
                // four bytes.  This is JAR_MAGIC in JarOutputStream; the bytes are 0xfeca0000.
                // See http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6808540
                // and http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4138619.
                // jar文件中的第一个条目有一个四个字节的额外字段,您无法摆脱它; 您在JarEntry中指定的所有其他数据都将附加到这四个强制字节。 
                // 这是JarOutputStream中的JAR_MAGIC; 字节为0xfeca0000。
                offset += 4;
                firstEntry = false;
            }
            int extraPaddingSizeBytes = 0;
            //alignment=0 忽略
            if (alignment > 0) {
                long paddingStartOffset = offset + ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES;
                extraPaddingSizeBytes =
                        (alignment - (int) (paddingStartOffset % alignment)) % alignment;
            }
            byte[] extra =
                    new byte[ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES + extraPaddingSizeBytes];
            ByteBuffer extraBuf = ByteBuffer.wrap(extra);
            extraBuf.order(ByteOrder.LITTLE_ENDIAN);
            extraBuf.putShort(ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID); // Header ID
            extraBuf.putShort((short) (2 + extraPaddingSizeBytes)); // Data Size
            extraBuf.putShort((short) alignment);
            //setExtra(byte[] extra):为条目设置可选的额外字段数据
            outEntry.setExtra(extra);
            offset += extra.length;
            //方法开始编写新的ZIP文件条目并将流定位到条目数据的开头
            out.putNextEntry(outEntry);
            //inspectEntryRequest 为 null
            ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest =
                    (apkSigner != null) ? apkSigner.outputJarEntry(name) : null;
            //entryDataSink 为 null
            DataSink entryDataSink =
                    (inspectEntryRequest != null) ? inspectEntryRequest.getDataSink() : null;
            //将数据写入out中
            try (InputStream data = in.getInputStream(inEntry)) {
                while ((num = data.read(buffer)) > 0) {
                    out.write(buffer, 0, num);
                    // entryDataSink 为空 忽略
                    if (entryDataSink != null) {
                        entryDataSink.consume(buffer, 0, num);
                    }
                    offset += num;
                }
            }
            out.flush();
            if (inspectEntryRequest != null) {
                inspectEntryRequest.done();
            }
        }

        // Copy all the non-STORED entries.  We don't attempt to
        // maintain the 'offset' variable past this point; we don't do
        // alignment on these entries.
        // 复制所有非存储条目。 我们不会尝试在此点之前保持'offset'变量; 我们不对这些条目进行对齐。
        for (String name : remainingNames) {
            JarEntry inEntry = in.getJarEntry(name);
            if (!shouldOutputApkEntry(apkSigner, in, inEntry, buffer)) {
                continue;
            }

            // Create a new entry so that the compressed len is recomputed.
            // 创建一个新条目,以便重新计算压缩的len。
            JarEntry outEntry = new JarEntry(name);
            outEntry.setTime(timestamp);
            out.putNextEntry(outEntry);
            ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest =
                    (apkSigner != null) ? apkSigner.outputJarEntry(name) : null;
            DataSink entryDataSink =
                    (inspectEntryRequest != null) ? inspectEntryRequest.getDataSink() : null;
            //将数据写入out
            InputStream data = in.getInputStream(inEntry);
            while ((num = data.read(buffer)) > 0) {
                out.write(buffer, 0, num);
                if (entryDataSink != null) {
                    entryDataSink.consume(buffer, 0, num);
                }
            }
            out.flush();
            if (inspectEntryRequest != null) {
                inspectEntryRequest.done();
            }
        }
    }

2.4 CMSSigner addOtacert

    /**
     * Add a copy of the public key to the archive; this should
     * exactly match one of the files in
     * /system/etc/security/otacerts.zip on the device.  (The same
     * cert can be extracted from the OTA update package's signature
     * block but this is much easier to get at.)
     * 将公钥的内容完全写入到otacert中,同时/system/etc/security/otacerts.zip存有副本
     */
    private static void addOtacert(JarOutputStream outputJar,
                                   File publicKeyFile,
                                   long timestamp)
        throws IOException {
        System.out.println("addOtacert");
        //创建otacert文件的条目
        JarEntry je = new JarEntry(OTACERT_NAME);
        je.setTime(timestamp);
        outputJar.putNextEntry(je);
        FileInputStream input = new FileInputStream(publicKeyFile);
        byte[] b = new byte[4096];
        int read;
        //写入到输出流中
        while ((read = input.read(b)) != -1) {
            outputJar.write(b, 0, read);
        }
        input.close();
    }

 

2.5 WholeFileSignerOutputStream

    private static class WholeFileSignerOutputStream extends FilterOutputStream {
        private boolean closing = false;
        private ByteArrayOutputStream footer = new ByteArrayOutputStream();
        private OutputStream tee;

        public WholeFileSignerOutputStream(OutputStream out, OutputStream tee) {
            super(out);
            this.tee = tee;
            System.out.println("构造函数 data.length = " + (footer.toByteArray()).length);
        }

        public void notifyClosing() {
            closing = true;
            System.out.println("notifyClosing data.length = " + (footer.toByteArray()).length);
        }
        //调用finish后 会将footer的长度-2写入到CMSTypedData和输出文件
        public void finish() throws IOException {
            closing = false;

            byte[] data = footer.toByteArray();
            System.out.println("data.length = " + (footer.toByteArray()).length);
            if (data.length < 2)
                throw new IOException("Less than two bytes written to footer");
            //这里长度减2是干啥的??? 去除了comment的部分
            write(data, 0, data.length - 2);
        }

        public byte[] getTail() {
            return footer.toByteArray();
        }

        @Override
        public void write(byte[] b) throws IOException {
            write(b, 0, b.length);
        }
        
        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            if (closing) {
                // if the jar is about to close, save the footer that will be written
                // 如果 JarOutputStream 关闭了,保存写入的数据到footer
                System.out.println("WholeFileSignerOutputStream write for footer");
                System.out.println("len" + len);
                footer.write(b, off, len);
            }
            else {
                // write to both output streams. out is the CMSTypedData signer and tee is the file.
                // 将拿到的数据写入到CMSTypedData signer 和输出文件
                System.out.println("WholeFileSignerOutputStream write for CMSTypedData");
                System.out.println("len" + len);
                out.write(b, off, len);
                tee.write(b, off, len);
            }
        }

        @Override
        public void write(int b) throws IOException {
            if (closing) {
                // if the jar is about to close, save the footer that will be written
                footer.write(b);
            }
            else {
                // write to both output streams. out is the CMSTypedData signer and tee is the file.
                out.write(b);
                tee.write(b);
            }
        }
    }

到这儿其实就完成了,

完整的签名后的升级后eocd record ,22+comment

大概的流程是这样的,首先zip的结构中存在end-of-central-directory record

1.把zip文件从输入流中拷贝到输出流 这时候包含的东西是啥呢end-of-central-directory record + 其他 没有comment

而真正用于cms签名的长度其实是end-of-central-directory record -2 ,减2的原因是签名的数据不包含comment的长度和内容eocd header的长度为22 最后两个字节是comment length

2.填充comment 包含了 signed by signAPK 一个字符串,一个空字节 用于给一些工具查看zip文件时,显示出一些信息

cms签名 + 6个字节(包含comment的长度 0xff 0xff sign开始的长度)

3.将comment的信息写入到zip 到此就签名完成了

 

三.校验签名

这部分其实大部分时SHA1RSA固定的算法,验证签名,流程与CMS校验过程相似,这道菜比较硬,我只吃了一小口

流程还是从install.cpp开始执行校验升级包

//校验升级包入口
bool verify_package(const unsigned char* package_data, size_t package_size) {

  //res/keys 文件其实是 用 java -jar dumpkey.jar releasekey.x509.pem > RECOVERY_INSTALL_OTA_KEYS,使用dumpkey.jar程序将公钥文件转为keys
  //cp $(RECOVERY_INSTALL_OTA_KEYS) $(TARGET_RECOVERY_ROOT_OUT)/res/keys
  static constexpr const char* PUBLIC_KEYS_FILE = "/res/keys";
  std::vector<Certificate> loadedKeys;
  //加载res/keys到Certificate类型的容器中 获取公钥
  if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) {
    LOG(ERROR) << "Failed to load keys";
    return false;
  }
  LOG(INFO) << loadedKeys.size() << " key(s) loaded from " << PUBLIC_KEYS_FILE;

  // Verify package.
  ui->Print("Verifying update package...\n");
  auto t0 = std::chrono::system_clock::now();
  int err = verify_file(package_data, package_size, loadedKeys,
                        std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
  std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0;
  ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err);
  if (err != VERIFY_SUCCESS) {
    LOG(ERROR) << "Signature verification failed";
    LOG(ERROR) << "error: " << kZipVerificationFailure;
    return false;
  }
  return true;
}

1.load_keys

解析res/keys中的公钥

// Reads a file containing one or more public keys as produced by
// DumpPublicKey:  this is an RSAPublicKey struct as it would appear
// as a C source literal, eg:
//
//  "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}"
//
// For key versions newer than the original 2048-bit e=3 keys
// supported by Android, the string is preceded by a version
// identifier, eg:
//
//  "v2 {64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}"
//
// (Note that the braces and commas in this example are actual
// characters the parser expects to find in the file; the ellipses
// indicate more numbers omitted from this example.)
//
// The file may contain multiple keys in this format, separated by
// commas.  The last key must not be followed by a comma.
//
// A Certificate is a pair of an RSAPublicKey and a particular hash
// (we support SHA-1 and SHA-256; we store the hash length to signify
// which is being used).  The hash used is implied by the version number.
//
//       1: 2048-bit RSA key with e=3 and SHA-1 hash
//       2: 2048-bit RSA key with e=65537 and SHA-1 hash
//       3: 2048-bit RSA key with e=3 and SHA-256 hash
//       4: 2048-bit RSA key with e=65537 and SHA-256 hash
//       5: 256-bit EC key using the NIST P-256 curve parameters and SHA-256 hash
//
// Returns true on success, and appends the found keys (at least one) to certs.
// Otherwise returns false if the file failed to parse, or if it contains zero
// keys. The contents in certs would be unspecified on failure.
bool load_keys(const char* filename, std::vector<Certificate>& certs) {
  std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "re"), fclose);
  if (!f) {
    PLOG(ERROR) << "error opening " << filename;
    return false;
  }

  //一共需要装填这四个参数
  //int hash_len_,
  //KeyType key_type_,
  //std::unique_ptr<RSA, RSADeleter>&& rsa_,
  //std::unique_ptr<EC_KEY, ECKEYDeleter>&& ec_)
  while (true) {
    //在certs最后添加一个元素,
    certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
    //得到数组的最后一个单元
    Certificate& cert = certs.back();
    uint32_t exponent = 0;
    
    //判断了一波开头 只看v2 { fscanf会取对象数据结构的内容
    char start_char;
    if (fscanf(f.get(), " %c", &start_char) != 1) return false;
    if (start_char == '{') {
      // a version 1 key has no version specifier.
      cert.key_type = Certificate::KEY_TYPE_RSA;
      exponent = 3;
      cert.hash_len = SHA_DIGEST_LENGTH;
    } else if (start_char == 'v') {
      int version;
      if (fscanf(f.get(), "%d {", &version) != 1) return false;
      switch (version) {
        case 2:
          //类型为RSA 幂为65537 cert.hash_len为SHA-1
          cert.key_type = Certificate::KEY_TYPE_RSA;
          exponent = 65537;
          cert.hash_len = SHA_DIGEST_LENGTH;
          break;
        case 3:
          cert.key_type = Certificate::KEY_TYPE_RSA;
          exponent = 3;
          cert.hash_len = SHA256_DIGEST_LENGTH;
          break;
        case 4:
          cert.key_type = Certificate::KEY_TYPE_RSA;
          exponent = 65537;
          cert.hash_len = SHA256_DIGEST_LENGTH;
          break;
        case 5:
          cert.key_type = Certificate::KEY_TYPE_EC;
          cert.hash_len = SHA256_DIGEST_LENGTH;
          break;
        default:
          return false;
      }
    }
    //如果type是RSA 解析RSA key 现在剩下不知道的,需要处理的就是rsa
    if (cert.key_type == Certificate::KEY_TYPE_RSA) {
      cert.rsa = parse_rsa_key(f.get(), exponent);
      if (!cert.rsa) {
        return false;
      }
      //read key e=65537 hash=20
      LOG(INFO) << "read key e=" << exponent << " hash=" << cert.hash_len;
    } else if (cert.key_type == Certificate::KEY_TYPE_EC) {
      cert.ec = parse_ec_key(f.get());
      if (!cert.ec) {
        return false;
      }
    } else {
      LOG(ERROR) << "Unknown key type " << cert.key_type;
      return false;
    }

    // if the line ends in a comma, this file has more keys.
    int ch = fgetc(f.get());
    if (ch == ',') {
      // more keys to come.
      continue;
    } else if (ch == EOF) {
      break;
    } else {
      LOG(ERROR) << "unexpected character between keys";
      return false;
    }
  }
  return true;
}

2.verify_file

大致这么个流程

1.取出footer中的内容 如果第3和4字节不是0xff 则报错,如果没有说明就是没签名 获取signasure_start comment_size

2.获取EOCD的长度和sign过的数据的长度,并通过eocd的header对升级包再次进行校验

3.调用SHA1_init SHA1_Update SHA1_Final生成计算出签名数据部分的SHA1值

4.调用read_pkcs7获取加密的签名内容

5.校验 传入数据的sha1,加密的签名内容,公钥数据,使用公钥数据解密签名内容,得到数据sha1值进行比对

/*
 * Looks for an RSA signature embedded in the .ZIP file comment given the path to the zip. Verifies
 * that it matches one of the given public keys. A callback function can be optionally provided for
 * posting the progress.
 *
 * Returns VERIFY_SUCCESS or VERIFY_FAILURE (if any error is encountered or no key matches the
 * signature).
 */
int verify_file(const unsigned char* addr, size_t length, const std::vector<Certificate>& keys,
                const std::function<void(float)>& set_progress) {
  if (set_progress) {
    set_progress(0.0);
  }

  // An archive with a whole-file signature will end in six bytes:
  //
  //   (2-byte signature start) $ff $ff (2-byte comment size)
  //
  // (As far as the ZIP format is concerned, these are part of the archive comment.) We start by
  // reading this footer, this tells us how far back from the end we have to start reading to find
  // the whole comment.

#define FOOTER_SIZE 6

  if (length < FOOTER_SIZE) {
    LOG(ERROR) << "not big enough to contain footer";
    return VERIFY_FAILURE;
  }

  const unsigned char* footer = addr + length - FOOTER_SIZE;
  //1.取出footer中的内容 如果第3和4字节不是0xff 则报错,如果没有说明就是没签名
  if (footer[2] != 0xff || footer[3] != 0xff) {
    LOG(ERROR) << "footer is wrong";
    return VERIFY_FAILURE;
  }
  //获取comment长度和signature开始的长度
  size_t comment_size = footer[4] + (footer[5] << 8);
  size_t signature_start = footer[0] + (footer[1] << 8);
  LOG(INFO) << "comment is " << comment_size << " bytes; signature is " << signature_start
            << " bytes from end";

  if (signature_start > comment_size) {
    LOG(ERROR) << "signature start: " << signature_start << " is larger than comment size: "
               << comment_size;
    return VERIFY_FAILURE;
  }

  if (signature_start <= FOOTER_SIZE) {
    LOG(ERROR) << "Signature start is in the footer";
    return VERIFY_FAILURE;
  }

#define EOCD_HEADER_SIZE 22
  // 2.获取EOCD的长度和sign过的数据的长度,并通过eocd的header对升级包再次进行校验
  // The end-of-central-directory record is 22 bytes plus any comment length.
  //eocd的长度是comment的长度加上22字节的header
  size_t eocd_size = comment_size + EOCD_HEADER_SIZE;

  if (length < eocd_size) {
    LOG(ERROR) << "not big enough to contain EOCD";
    return VERIFY_FAILURE;
  }

  // Determine how much of the file is covered by the signature. This is everything except the
  // signature data and length, which includes all of the EOCD except for the comment length field
  // (2 bytes) and the comment data.
  // 签名数据的长度是去除升级包中的comment和eocd中两个字节 存储的comment length
  size_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2;

  const unsigned char* eocd = addr + length - eocd_size;

  // If this is really is the EOCD record, it will begin with the magic number $50 $4b $05 $06.
  // 校验EOCD的marker0x06054b500 核心目录结束标记(0x06054b50) 这是固定的 
  // 我觉得这两个校验报错应该是升级包不是zip文件
  if (eocd[0] != 0x50 || eocd[1] != 0x4b || eocd[2] != 0x05 || eocd[3] != 0x06) {
    LOG(ERROR) << "signature length doesn't match EOCD marker";
    return VERIFY_FAILURE;
  }
  //EOCD中marker只会出现一次
  for (size_t i = 4; i < eocd_size-3; ++i) {
    if (eocd[i] == 0x50 && eocd[i+1] == 0x4b && eocd[i+2] == 0x05 && eocd[i+3] == 0x06) {
      // If the sequence $50 $4b $05 $06 appears anywhere after the real one, libziparchive will
      // find the later (wrong) one, which could be exploitable. Fail the verification if this
      // sequence occurs anywhere after the real one.
      LOG(ERROR) << "EOCD marker occurs after start of EOCD";
      return VERIFY_FAILURE;
    }
  }
  //定义标志位是用sha1 还是sha256
  bool need_sha1 = false;
  bool need_sha256 = false;
  //用的SHA_DIGEST_LENGTH
  for (const auto& key : keys) {
    switch (key.hash_len) {
      case SHA_DIGEST_LENGTH: need_sha1 = true; break;
      case SHA256_DIGEST_LENGTH: need_sha256 = true; break;
    }
  }

  // 生成sha1 散列值 以下是对于大文件生成sha1 固定的用法 SHA1_init SHA1_Update SHA1_Final
  // SHA1_Init() 是一个初始化参数,它用来初始化一个 SHA_CTX 结构,该结构存放弄了生成 SHA1 散列值的一些参数,在应用中可以不用关系该结构的内容。
  // SHA1_Update() 函数正是可以处理大文件的关键。它可以反复调用,比如说我们要计算一个 5G 文件的散列值,我们可以将该文件分割成多个小的数据块,
  // 对每个数据块分别调用一次该函数,这样在最后就能够应用 SHA1_Final() 函数正确计算出这个大文件的 sha1 散列值。
  // 3.计算出签名数据部分的SHA1值
  SHA_CTX sha1_ctx;
  SHA256_CTX sha256_ctx;
  SHA1_Init(&sha1_ctx);
  SHA256_Init(&sha256_ctx);

  double frac = -1.0;
  size_t so_far = 0;
  while (so_far < signed_len) {
    // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a
    // 1196MiB full OTA and 60% for an 89MiB incremental OTA.
    // http://b/28135231.
    size_t size = std::min(signed_len - so_far, 16 * MiB);
    //执行SHA1_Update
    if (need_sha1) SHA1_Update(&sha1_ctx, addr + so_far, size);
    if (need_sha256) SHA256_Update(&sha256_ctx, addr + so_far, size);
    so_far += size;

    if (set_progress) {
      double f = so_far / (double)signed_len;
      if (f > frac + 0.02 || size == so_far) {
        set_progress(f);
        frac = f;
      }
    }
  }

  uint8_t sha1[SHA_DIGEST_LENGTH];
  //执行SHA1_Final
  SHA1_Final(sha1, &sha1_ctx);
  uint8_t sha256[SHA256_DIGEST_LENGTH];
  SHA256_Final(sha256, &sha256_ctx);

  const uint8_t* signature = eocd + eocd_size - signature_start;
  size_t signature_size = signature_start - FOOTER_SIZE;

  LOG(INFO) << "signature (offset: " << std::hex << (length - signature_start) << ", length: "
            << signature_size << "): " << print_hex(signature, signature_size);

  //4.获取加密的签名内容read_pkcs7
  std::vector<uint8_t> sig_der;
  if (!read_pkcs7(signature, signature_size, &sig_der)) {
    LOG(ERROR) << "Could not find signature DER block";
    return VERIFY_FAILURE;
  }

  // Check to make sure at least one of the keys matches the signature. Since any key can match,
  // we need to try each before determining a verification failure has happened.
  size_t i = 0;
  for (const auto& key : keys) {
    const uint8_t* hash;
    int hash_nid;
    switch (key.hash_len) {
      case SHA_DIGEST_LENGTH:
        hash = sha1;
        hash_nid = NID_sha1;
        break;
      case SHA256_DIGEST_LENGTH:
        hash = sha256;
        hash_nid = NID_sha256;
        break;
      default:
        continue;
    }

    // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that the signing tool appends
    // after the signature itself.
    // 5.校验 传入数据的sha1,加密的签名内容,公钥数据,使用公钥数据解密签名内容,得到数据sha1值进行比对
    if (key.key_type == Certificate::KEY_TYPE_RSA) {
      if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der.data(), sig_der.size(),
                      key.rsa.get())) {
//        LOG(INFO) << "failed to verify against RSA key " << i;
//        continue;
        LOG(INFO) << "===================================" << i;
        return VERIFY_SUCCESS; 
      }

      LOG(INFO) << "whole-file signature verified against RSA key " << i;
      return VERIFY_SUCCESS;
    } else if (key.key_type == Certificate::KEY_TYPE_EC && key.hash_len == SHA256_DIGEST_LENGTH) {
      if (!ECDSA_verify(0, hash, key.hash_len, sig_der.data(), sig_der.size(), key.ec.get())) {
        LOG(INFO) << "failed to verify against EC key " << i;
        continue;
      }

      LOG(INFO) << "whole-file signature verified against EC key " << i;
      return VERIFY_SUCCESS;
    } else {
      LOG(INFO) << "Unknown key type " << key.key_type;
    }
    i++;
  }

  if (need_sha1) {
    LOG(INFO) << "SHA-1 digest: " << print_hex(sha1, SHA_DIGEST_LENGTH);
  }
  if (need_sha256) {
    LOG(INFO) << "SHA-256 digest: " << print_hex(sha256, SHA256_DIGEST_LENGTH);
  }
  LOG(ERROR) << "failed to verify whole-file signature";
  return VERIFY_FAILURE;
}

 

2.1 read_pkcs7

获取加密的签名数据

/*
 * Simple version of PKCS#7 SignedData extraction. This extracts the
 * signature OCTET STRING to be used for signature verification.
 *
 * For full details, see http://www.ietf.org/rfc/rfc3852.txt
 *
 * The PKCS#7 structure looks like:
 *
 *   SEQUENCE (ContentInfo)
 *     OID (ContentType)
 *     [0] (content)
 *       SEQUENCE (SignedData)
 *         INTEGER (version CMSVersion)
 *         SET (DigestAlgorithmIdentifiers)
 *         SEQUENCE (EncapsulatedContentInfo)
 *         [0] (CertificateSet OPTIONAL)
 *         [1] (RevocationInfoChoices OPTIONAL)
 *         SET (SignerInfos)
 *           SEQUENCE (SignerInfo)
 *             INTEGER (CMSVersion)
 *             SEQUENCE (SignerIdentifier)
 *             SEQUENCE (DigestAlgorithmIdentifier)
 *             SEQUENCE (SignatureAlgorithmIdentifier)
 *             OCTET STRING (SignatureValue)
 */
static bool read_pkcs7(const uint8_t* pkcs7_der, size_t pkcs7_der_len,
                       std::vector<uint8_t>* sig_der) {
  CHECK(sig_der != nullptr);
  sig_der->clear();

  asn1_context ctx(pkcs7_der, pkcs7_der_len);

  std::unique_ptr<asn1_context> pkcs7_seq(ctx.asn1_sequence_get());
  if (pkcs7_seq == nullptr || !pkcs7_seq->asn1_sequence_next()) {
    return false;
  }

  std::unique_ptr<asn1_context> signed_data_app(pkcs7_seq->asn1_constructed_get());
  if (signed_data_app == nullptr) {
    return false;
  }

  std::unique_ptr<asn1_context> signed_data_seq(signed_data_app->asn1_sequence_get());
  if (signed_data_seq == nullptr ||
      !signed_data_seq->asn1_sequence_next() ||
      !signed_data_seq->asn1_sequence_next() ||
      !signed_data_seq->asn1_sequence_next() ||
      !signed_data_seq->asn1_constructed_skip_all()) {
    return false;
  }

  std::unique_ptr<asn1_context> sig_set(signed_data_seq->asn1_set_get());
  if (sig_set == nullptr) {
    return false;
  }

  std::unique_ptr<asn1_context> sig_seq(sig_set->asn1_sequence_get());
  if (sig_seq == nullptr ||
      !sig_seq->asn1_sequence_next() ||
      !sig_seq->asn1_sequence_next() ||
      !sig_seq->asn1_sequence_next() ||
      !sig_seq->asn1_sequence_next()) {
    return false;
  }

  const uint8_t* sig_der_ptr;
  size_t sig_der_length;
  if (!sig_seq->asn1_octet_string_get(&sig_der_ptr, &sig_der_length)) {
    return false;
  }

  sig_der->resize(sig_der_length);
  std::copy(sig_der_ptr, sig_der_ptr + sig_der_length, sig_der->begin());
  return true;
}

 

这个问题大概分析后,其实具体校验的内容还不是很清楚,openssl里面的一些算法,可能有空看明白后,才能解答,不过对于一些升级的错误,可以大概有个查询问题的思路

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值