Add command to check presigned APKs

This commit is contained in:
Danny Lin 2021-11-07 20:01:41 -08:00
parent 146ff625a0
commit 056e184840
5 changed files with 129 additions and 30 deletions

View file

@ -1,10 +1,10 @@
import { promises as fs } from 'fs'
import * as path from 'path'
import * as chalk from 'chalk'
import * as ora from 'ora'
import { EXT_PARTITIONS } from '../partitions'
import { BlobEntry } from './entry'
import { exists, listFilesRecursive } from '../util/fs'
// Sub-partition directories to ignore
const IGNORE_DIRS = new Set([
@ -152,6 +152,10 @@ system_ext/priv-app/FactoryOta/FactoryOta.apk`,
product/wallpaper/`,
])
function paths(blocks: Array<string>) {
return blocks.flatMap(b => b.split('\n'))
}
export function parseFileList(list: string) {
let entries = []
@ -195,33 +199,6 @@ export function parseFileList(list: string) {
return entries.sort((a, b) => a.srcPath.localeCompare(b.srcPath))
}
function paths(blocks: Array<string>) {
return blocks.flatMap(b => b.split('\n'))
}
// https://stackoverflow.com/a/45130990
async function* listFilesRecursive(dir: string): AsyncGenerator<string> {
const dirents = await fs.readdir(dir, { withFileTypes: true })
for (const dirent of dirents) {
const res = path.resolve(dir, dirent.name)
if (dirent.isDirectory()) {
yield* listFilesRecursive(res)
} else if (dirent.isFile() || dirent.isSymbolicLink()) {
yield res
}
}
}
async function exists(path: string) {
try {
await fs.access(path)
return true
} catch {
// Doesn't exist or can't read
return false
}
}
export async function listPart(partition: string, systemRoot: string) {
let partRoot = `${systemRoot}/${partition}`
if (!await exists(partRoot)) {

View file

@ -0,0 +1,66 @@
import { Command, flags } from '@oclif/command'
import { promises as fs } from 'fs'
import * as path from 'path'
import * as chalk from 'chalk'
import { $ } from 'zx'
import { parseFileList } from '../blobs/file_list'
import { listFilesRecursive } from '../util/fs'
import { parseSeappContexts } from '../sepolicy/parse_seapp'
$.verbose = false
export default class CheckPresigned extends Command {
static description = 'check for APKs that should be presigned'
static flags = {
help: flags.help({char: 'h'}),
aapt2: flags.string({char: 'a', description: 'path to aapt2 executable', default: 'out/host/linux-x86/bin/aapt2'}),
sepolicy: flags.string({char: 's', description: 'paths to device and vendor sepolicy dirs', required: true, multiple: true}),
}
static args = [
{name: 'source', description: 'path to mounted factory images', required: true},
{name: 'listPath', description: 'path to LineageOS-compatible proprietary-files.txt list', required: true},
]
async run() {
let {flags: {aapt2, sepolicy}, args: {source, listPath}} = this.parse(CheckPresigned)
// Parse list
this.log(chalk.bold(chalk.greenBright('Parsing list')))
let list = await fs.readFile(listPath, {encoding: 'utf8'})
let entries = parseFileList(list)
// Find and parse sepolicy seapp_contexts
let contexts = []
for (let dir of sepolicy) {
for await (let file of listFilesRecursive(dir)) {
if (path.basename(file) != 'seapp_contexts') {
continue
}
let rawContexts = await fs.readFile(file, { encoding: 'utf8' })
contexts.push(...parseSeappContexts(rawContexts))
}
}
let presignedPkgs = new Set(contexts.filter(c => c.seinfo != 'platform')
.map(c => c.name))
// Process APKs
for (let entry of entries) {
if (path.extname(entry.path) != '.apk') {
continue
}
let procOut = await $`${aapt2} dump packagename ${source}/${entry.srcPath}`
let pkgName = procOut.stdout.trim()
if (presignedPkgs.has(pkgName)) {
entry.isPresigned = true
this.log(entry.srcPath)
}
}
// TODO: write new list
}
}

View file

@ -29,16 +29,16 @@ export default class Extract extends Command {
help: flags.help({char: 'h'}),
vendor: flags.string({char: 'v', description: 'device vendor/OEM name', required: true}),
device: flags.string({char: 'd', description: 'device codename', required: true}),
source: flags.string({char: 's', description: 'path to mounted factory images', required: true}),
skipCopy: flags.boolean({char: 'k', description: 'skip file copying and only generate build files'}),
}
static args = [
{name: 'source', description: 'path to mounted factory images', required: true},
{name: 'listPath', description: 'path to LineageOS-compatible proprietary-files.txt list', required: true},
]
async run() {
let {args: {listPath}, flags: {vendor, device, source, skipCopy}} = this.parse(Extract)
let {args: {source, listPath}, flags: {vendor, device, skipCopy}} = this.parse(Extract)
// Parse list
this.log(chalk.bold(chalk.greenBright('Parsing list')))

View file

@ -0,0 +1,31 @@
export interface SeappContext {
user: string
seinfo?: string
isPrivApp?: boolean
name: string
domain: string
type?: string
levelFrom: string
}
export function parseSeappContexts(seappContexts: string) {
let contexts = []
for (let line of seappContexts.split('\n')) {
// Ignore comments and empty/blank lines
if (line.length == 0 || line.startsWith('#') || line.match(/^\s*$/)) {
continue
}
// Parse key-value fields
let rawContext: { [key: string]: string } = {}
for (let kv of line.trim().split(/\s+/)) {
let [key, value] = kv.split('=')
rawContext[key] = value
}
// Cast directly to object
contexts.push(rawContext as unknown as SeappContext)
}
return contexts
}

25
src/util/fs.ts Normal file
View file

@ -0,0 +1,25 @@
import { promises as fs } from 'fs'
import * as path from 'path'
// https://stackoverflow.com/a/45130990
export async function* listFilesRecursive(dir: string): AsyncGenerator<string> {
const dirents = await fs.readdir(dir, { withFileTypes: true })
for (const dirent of dirents) {
const res = path.resolve(dir, dirent.name)
if (dirent.isDirectory()) {
yield* listFilesRecursive(res)
} else if (dirent.isFile() || dirent.isSymbolicLink()) {
yield res
}
}
}
export async function exists(path: string) {
try {
await fs.access(path)
return true
} catch {
// Doesn't exist or can't read
return false
}
}