Android Runtime代码签名与验证流程剖析(84)

126 阅读9分钟

Android Runtime代码签名与验证流程剖析

一、代码签名与验证概述

在Android系统中,代码签名与验证是保障应用程序安全运行的重要机制。通过对应用代码进行签名,并在安装、运行过程中进行严格验证,能够确保应用来源可信、代码未被篡改,防止恶意软件伪装成合法应用入侵系统。Android Runtime(ART)作为应用运行的核心环境,深度参与了代码签名与验证的全流程,从应用安装时的初步校验,到运行时的类加载检查,构建起多层防护体系。接下来,我们将从源码层面逐步解析这一复杂且关键的安全流程。

二、Android代码签名基础

2.1 签名原理与格式

Android应用的签名基于公钥密码学体系,开发者使用私钥对应用的二进制文件(通常是APK)进行签名,生成签名信息。用户设备则使用对应的公钥验证签名,以此确认代码的完整性和来源可信性。

签名文件存储在APK的META-INF目录下,常见的签名文件包括.RSA(包含签名数据)、.SF(包含文件摘要列表)等。在frameworks/base/cmds/signapk/目录下的signapk.jar文件,提供了签名生成的核心逻辑:

// frameworks/base/cmds/signapk/signapk.jar
public class SignApk {
    // 使用私钥对APK进行签名
    public static void sign(
        PrivateKey privateKey,  // 开发者私钥
        X509Certificate[] certificateChain,  // 证书链
        File inputApk,  // 待签名的APK文件
        File outputApk) throws IOException, GeneralSecurityException {
        // 解析APK文件内容
        ZipFile zipInput = new ZipFile(inputApk);
        ZipOutputStream zipOutput = new ZipOutputStream(new FileOutputStream(outputApk));
        
        // 生成签名文件摘要
        Manifest manifest = generateManifest(zipInput);
        generateSignatureFile(manifest, privateKey);
        
        // 将原始文件、签名文件写入新APK
        copyFiles(zipInput, zipOutput);
        addSignatureFiles(zipOutput, certificateChain, privateKey);
        
        zipInput.close();
        zipOutput.close();
    }
}

上述代码中,generateManifest方法遍历APK内的所有文件,计算其摘要并生成MANIFEST.MF文件;generateSignatureFile使用私钥对MANIFEST.MF进行签名,生成.SF文件;最后通过addSignatureFiles将签名文件和证书链写入APK,完成签名过程。

2.2 数字证书体系

Android的签名验证依赖数字证书,证书包含了开发者身份信息、公钥以及证书颁发机构(CA)的签名。系统通过信任的根证书验证应用证书的合法性。

证书结构在org.apache.harmony.xnet.provider.jsse.X509CertificateImpl类(位于libcore/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/目录)中定义:

// libcore/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/X509CertificateImpl.java
public class X509CertificateImpl implements X509Certificate {
    private final byte[] encoded;  // 证书编码数据
    private final Principal subjectDN;  // 证书主体(开发者)
    private final Principal issuerDN;  // 证书颁发者
    private final Date notBefore;  // 证书生效时间
    private final Date notAfter;  // 证书过期时间
    
    // 验证证书是否有效
    @Override
    public boolean isValid(Date date) {
        return date.after(notBefore) && date.before(notAfter);
    }
    
    // 获取证书公钥
    @Override
    public PublicKey getPublicKey() {
        // 从证书数据中解析公钥
        return parsePublicKey(encoded); 
    }
}

系统在验证时,会检查证书是否在有效期内,并通过根证书验证证书链的可信性,确保签名来源可靠。

三、应用安装阶段的签名验证

3.1 PackageManagerService的初始校验

在应用安装过程中,PackageManagerService(PMS)负责核心的验证工作。PackageManagerService位于frameworks/base/services/core/java/com/android/server/pm/目录,其验证流程如下:

// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public class PackageManagerService {
    // 解析APK并验证签名
    private PackageParser.Package parsePackage(
        File packageFile,  // APK文件路径
        int flags) {
        PackageParser parser = new PackageParser(packageFile);
        PackageParser.Package pkg = parser.parsePackage(packageFile, flags);
        
        // 验证APK签名
        if (!verifySignatures(pkg)) {
            throw new PackageManager.NameNotFoundException(
                "Package has invalid signatures");
        }
        
        return pkg;
    }
    
    // 核心签名验证逻辑
    private boolean verifySignatures(PackageParser.Package pkg) {
        // 获取APK中的签名信息
        Signature[] signatures = pkg.signatures;
        if (signatures == null || signatures.length == 0) {
            return false;
        }
        
        // 检查签名是否与系统信任的证书匹配
        for (Signature signature : signatures) {
            if (!isSignatureTrusted(signature)) {
                return false;
            }
        }
        
        return true;
    }
}

parsePackage方法调用PackageParser解析APK文件,并通过verifySignatures验证签名。verifySignatures会对比APK中的签名与系统信任的证书列表,若不匹配则终止安装。

3.2 签名验证的细节处理

在验证过程中,系统会检查签名文件的完整性和一致性。PackageParser在解析APK时,会验证MANIFEST.MF.SF.RSA文件之间的关联:

// frameworks/base/core/java/android/content/pm/PackageParser.java
public class PackageParser {
    // 解析APK中的签名信息
    private void parseSignature(ZipFile zip) throws PackageParserException {
        ZipEntry manifestEntry = zip.getEntry("META-INF/MANIFEST.MF");
        ZipEntry sfEntry = zip.getEntry("META-INF/*.SF");
        ZipEntry rsaEntry = zip.getEntry("META-INF/*.RSA");
        
        // 验证MANIFEST.MF的完整性
        if (!verifyManifest(manifestEntry)) {
            throw new PackageParserException("Invalid MANIFEST.MF");
        }
        
        // 验证.SF文件与MANIFEST.MF的一致性
        if (!verifySignatureFile(sfEntry, manifestEntry)) {
            throw new PackageParserException("Invalid signature file");
        }
        
        // 验证.RSA文件的签名有效性
        if (!verifySignatureBlock(rsaEntry, sfEntry)) {
            throw new PackageParserException("Invalid signature block");
        }
    }
    
    // 验证MANIFEST.MF文件
    private boolean verifyManifest(ZipEntry entry) {
        // 读取文件内容并计算摘要
        byte[] data = readEntryData(entry);
        byte[] calculatedDigest = calculateDigest(data);
        
        // 对比文件中记录的摘要与计算结果
        String storedDigest = getStoredDigest(entry);
        return Arrays.equals(calculatedDigest, storedDigest);
    }
}

通过逐层验证签名文件间的依赖关系,确保APK在签名后未被篡改。

四、ART类加载阶段的签名验证

4.1 ClassLinker的验证逻辑

当应用运行时,ART的ClassLinker负责类的加载与验证。在art/runtime/class_linker.cc中,类加载时会检查类文件的签名:

// art/runtime/class_linker.cc
ObjPtr<mirror::Class> ClassLinker::DefineClass(
    Thread* self,
    const char* descriptor,
    Handle<mirror::ClassLoader> class_loader,
    const DexFile& dex_file,
    size_t class_def_idx) {
    // 获取Dex文件的签名
    const Signature& dexSignature = dex_file.GetSignature();
    
    // 检查类加载器的签名与Dex文件是否匹配
    if (!class_loader->HasMatchingSignature(dexSignature)) {
        self->ThrowIllegalAccessError("Class signature mismatch");
        return nullptr;
    }
    
    // 其他类定义操作
    //...
    return result;
}

DefineClass方法获取Dex文件的签名,并与加载该类的类加载器签名进行比对。若签名不匹配,将抛出异常并阻止类的加载。

4.2 签名验证与ClassLoader的协作

Android的ClassLoader在加载类时,也会参与签名验证。例如,PathClassLoaderframeworks/base/core/java/dalvik/system/目录下,其加载逻辑如下:

// frameworks/base/core/java/dalvik/system/PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(
        String dexPath,  // APK路径
        ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 从Dex文件中加载类
        Class<?> clazz = super.findClass(name);
        
        // 验证类的签名
        if (!verifyClassSignature(clazz)) {
            throw new SecurityException("Class signature verification failed");
        }
        
        return clazz;
    }
    
    // 类签名验证方法
    private boolean verifyClassSignature(Class<?> clazz) {
        // 获取类所属的Dex文件签名
        Signature dexSignature = getDexSignature(clazz);
        // 获取当前ClassLoader的签名
        Signature loaderSignature = getClassLoaderSignature();
        
        // 对比签名
        return dexSignature.equals(loaderSignature);
    }
}

findClass方法在加载类后,调用verifyClassSignature验证类的签名,确保运行的代码来源可信。

五、动态代码加载的签名验证

5.1 JNI代码的签名检查

当应用通过JNI调用本地代码时,ART同样会验证代码的签名。在art/runtime/jni/jni_dlsym.cc中,加载本地库时的验证逻辑如下:

// art/runtime/jni/jni_dlsym.cc
void* JniDlOpen(const char* library_name, int flags) {
    // 打开本地库文件
    void* handle = dlopen(library_name, flags);
    if (handle == nullptr) {
        return nullptr;
    }
    
    // 验证库文件的签名
    if (!VerifyLibrarySignature(library_name)) {
        dlclose(handle);
        return nullptr;
    }
    
    return handle;
}

// 验证本地库签名
bool VerifyLibrarySignature(const char* library_name) {
    // 获取库文件的签名信息
    Signature librarySignature = GetLibrarySignature(library_name);
    // 获取应用的签名
    Signature appSignature = GetAppSignature();
    
    // 对比签名
    return librarySignature.equals(appSignature);
}

JniDlOpen方法在打开本地库后,通过VerifyLibrarySignature检查库的签名是否与应用签名一致,不一致则拒绝加载。

5.2 插件化与热修复的签名验证

对于插件化和热修复场景,系统对动态加载的代码同样有严格的验证要求。以插件化为例,在加载插件APK时,PluginManager(假设存在)会执行类似应用安装的签名验证流程:

// 假设的PluginManager类
public class PluginManager {
    // 加载插件APK
    public Plugin loadPlugin(File pluginFile) {
        // 解析插件APK
        PackageParser.Package pkg = parsePackage(pluginFile);
        
        // 验证插件签名
        if (!verifySignatures(pkg)) {
            throw new PluginLoadException("Plugin has invalid signatures");
        }
        
        // 加载插件中的类
        PluginClassLoader classLoader = new PluginClassLoader(pkg, getBaseClassLoader());
        return new Plugin(classLoader);
    }
}

通过复用应用安装阶段的签名验证逻辑,确保动态加载的代码同样安全可信。

六、系统更新与签名验证

6.1 OTA更新的签名验证

在系统进行OTA(Over-The-Air)更新时,签名验证是关键环节。UpdateEngine(位于system/update_engine/目录)负责验证更新包的签名:

// system/update_engine/update_engine.cc
bool UpdateEngine::VerifyUpdatePackage() {
    // 读取更新包中的签名信息
    Signature updateSignature = GetUpdateSignature();
    // 获取系统信任的证书
    X509Certificate[] trustedCertificates = GetTrustedCertificates();
    
    // 验证签名
    for (X509Certificate cert : trustedCertificates) {
        if (cert.verify(updateSignature)) {
            return true;
        }
    }
    
    return false;
}

VerifyUpdatePackage方法对比更新包签名与系统信任的证书,只有签名验证通过才能继续安装更新。

6.2 系统镜像签名验证

在设备启动过程中,Bootloader会验证系统镜像的签名。以aboot(位于bootable/bootloader/aboot/目录)为例:

// bootable/bootloader/aboot/aboot.c
int verify_system_image() {
    // 读取系统镜像的签名
    unsigned char* signature = read_image_signature();
    // 获取设备的公钥
    unsigned char* public_key = get_device_public_key();
    
    // 验证签名
    if (rsa_verify(signature, public_key)) {
        return 0;  // 验证通过
    } else {
        return -1; // 验证失败
    }
}

verify_system_image方法使用设备的公钥验证系统镜像签名,确保启动的系统未经篡改。

七、签名验证的错误处理与回滚机制

7.1 验证失败的处理逻辑

当签名验证失败时,系统会根据场景采取不同的处理方式。在应用安装阶段,PackageManagerService会终止安装并提示用户:

// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void handleSignatureVerificationFailure(
    PackageParser.Package pkg,
    String errorMessage) {
    // 记录错误日志
    Log.e(TAG, "Signature verification failed for " + pkg.packageName + ": " + errorMessage);
    // 向用户显示错误提示
    sendVerificationFailureNotification(pkg.packageName, errorMessage);
    // 清理临时安装文件
    cleanUpInstallationFiles(pkg);
}

在类加载或动态代码加载时,ART会抛出异常并阻止代码执行,防止恶意代码运行。

7.2 回滚与恢复机制

对于系统更新或应用安装失败的情况,系统支持回滚机制。在OTA更新中,UpdateEngine会保留旧版本系统,若验证失败则回滚:

// system/update_engine/update_engine.cc
void UpdateEngine::RollbackUpdate() {
    // 恢复旧版本系统镜像
    RestoreOldSystemImage();
    // 清除临时更新文件
    DeleteUpdateFiles();
    // 重启设备
    RebootDevice();
}

通过回滚机制,确保系统在签名验证失败时仍能保持稳定运行。

八、签名验证的性能优化

8.1 缓存与快速验证

为提升验证效率,系统会缓存已验证的签名信息。在PackageManagerService中,通过SignatureCache类实现缓存:

// frameworks/base/services/core/java/com/android/server/pm/SignatureCache.java
public class SignatureCache {
    private final Map<String, Signature> cache = new HashMap<>();  // 缓存签名
    
    // 获取缓存的签名
    public Signature get(String packageName) {
        return cache.get(packageName);
    }
    
    // 添加签名到缓存
    public void put(String packageName, Signature signature) {
        cache.put(packageName, signature);
    }
}

在验证时,优先从缓存中获取签名,减少重复验证开销。

8.2 并行验证与优化算法

在处理多个文件或大型APK时,系统采用并行验证和优化算法。例如,在验证APK内多个文件的签名时,PackageParser可采用多线程验证:

// frameworks/base/core/java/android/content/pm/PackageParser.java
private void verifySignaturesParallel(ZipFile zip) {
    int numFiles = zip.size();
    ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    
    for (int i = 0; i < numFiles; i++) {
        ZipEntry entry = zip.entries().nextElement();
        executor.submit(() -> {
            try {
                // 独立验证每个文件的签名
                verifyEntrySignature(entry); 
            } catch (Exception e) {
                // 处理验证错误
                handleVerificationError(entry, e); 
            }
        });
    }
    
    executor.shutdown();
    try {
        executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
    } catch (InterruptedException e) {
        // 处理中断异常
    }
}

通过并行处理和优化算法,显著提升了签名验证的效率。