Add command to check presigned APKs
This commit is contained in:
parent
146ff625a0
commit
056e184840
5 changed files with 129 additions and 30 deletions
|
@ -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)) {
|
||||
|
|
66
src/commands/check-presigned.ts
Normal file
66
src/commands/check-presigned.ts
Normal 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
|
||||
}
|
||||
}
|
|
@ -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')))
|
||||
|
|
31
src/sepolicy/parse_seapp.ts
Normal file
31
src/sepolicy/parse_seapp.ts
Normal 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
25
src/util/fs.ts
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue