recovery ota升级包签名生成/校验
https://blog.csdn.net/Android_2016/article/details/105494564?spm=1001.2014.3001.5501
最近一直有两个疑问 升级包签名的规则和签名文件具体的位置,所以大概看了下签名流程并整理出来
大概理解了下 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里面的一些算法,可能有空看明白后,才能解答,不过对于一些升级的错误,可以大概有个查询问题的思路
————————————————
版权声明:本文为CSDN博主「李标标」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Android_2016/article/details/105494564