diff --git a/src/sepolicy/keys.ts b/src/sepolicy/keys.ts new file mode 100644 index 0000000..df244db --- /dev/null +++ b/src/sepolicy/keys.ts @@ -0,0 +1,166 @@ +import { promises as fs } from 'fs' +import * as path from 'path' +import * as xml2js from 'xml2js' + +import { exists, listFilesRecursive } from '../util/fs' +import { parseLines } from '../util/parse' +import { EXT_SYS_PARTITIONS } from '../util/partitions' + +export interface MacSigner { + cert: string | Uint8Array + seinfoId: string +} + +export interface KeyInfo { + keyId: string + certPaths: Map +} + +function parseHex(hex: string) { + let buf = new Uint8Array(hex.length / 2) + for (let i = 0; i < hex.length; i += 2) { + buf[i / 2] = parseInt(hex.slice(i, i + 2), 16) + } + + return buf +} + +async function parseMacPermissions(xml: string) { + let doc = await xml2js.parseStringPromise(xml) + let signers = [] + + if (doc.policy) { + for (let { $: { signature: rawSig }, seinfo: [{ $: { value: seinfoId }}]} of doc.policy.signer) { + // Parse base64 cert or leave it as a reference + let cert = rawSig.startsWith('@') ? rawSig.slice(1) : parseHex(rawSig) + signers.push({ + cert: cert, + seinfoId: seinfoId, + } as MacSigner) + } + } + + return signers +} + +export async function readMacPermissionsRecursive(root: string) { + let signers = [] + for await (let file of listFilesRecursive(root)) { + if (path.basename(file) == 'mac_permissions.xml') { + let xml = await fs.readFile(file, { encoding: 'utf8' }) + signers.push(...(await parseMacPermissions(xml))) + } + } + + return signers +} + +export async function readPartMacPermissions(root: string) { + let signers = [] + for (let partition of EXT_SYS_PARTITIONS) { + let path = `${root}/${partition}/etc/selinux/${partition}_mac_permissions.xml` + if (!(await exists(path))) { + continue + } + + let xml = await fs.readFile(path, { encoding: 'utf8' }) + signers.push(...(await parseMacPermissions(xml))) + } + + return signers +} + +function parseKeysConf(conf: string) { + let curKeyId: string | null = null + let curPaths: Map | null = null + + let keys = [] + for (let line of parseLines(conf)) { + let startBlock = line.match(/^\[@(.+)\]$/) + if (startBlock != undefined) { + // Finish last block + if (curKeyId != null) { + keys.push({ + keyId: curKeyId, + certPaths: curPaths!, + } as KeyInfo) + } + + curKeyId = startBlock[1] + curPaths = new Map() + continue + } + + let pathLine = line.match(/^(.+)\s*:\s*(.+)$/) + if (pathLine != undefined) { + let [_, buildType, path] = pathLine + if (curPaths != null) { + curPaths.set(buildType, path) + } + } + } + + // Finish last block + if (curKeyId != null) { + keys.push({ + keyId: curKeyId, + certPaths: curPaths!, + } as KeyInfo) + } + + return keys +} + +export async function readKeysConfRecursive(root: string) { + let keys = [] + for await (let file of listFilesRecursive(root)) { + if (path.basename(file) == 'keys.conf') { + let xml = await fs.readFile(file, { encoding: 'utf8' }) + keys.push(...parseKeysConf(xml)) + } + } + + return keys +} + +export function resolveKeys(srcKeys: Array, srcMacPerms: Array, compiledMacPerms: Array) { + console.log({ + srcKeys: srcKeys, + srcMacPerms: srcMacPerms, + compiledMacPerms: compiledMacPerms, + }) + // Build key ID -> paths map + let keyToPaths = new Map(srcKeys.map(k => [k.keyId, Array.from(k.certPaths.values())])) + + // Build seinfo -> paths map + let seinfoToPaths = new Map(srcMacPerms.filter(s => typeof s.cert == 'string') + .map(s => [s.seinfoId, keyToPaths.get(s.cert as string)!])) + + // Build cert -> paths map + return new Map(compiledMacPerms.filter(s => seinfoToPaths.has(s.seinfoId) && s.cert instanceof Uint8Array) + .map(s => [s.cert as Uint8Array, seinfoToPaths.get(s.seinfoId)!])) +} + +function serializeCert(cert: Uint8Array) { + let base64 = Buffer.from(cert).toString('base64') + // Wrap to 76 chars to match Google's PEMs + let wrapped = base64.replace(/(.{76})/g, '$1\n') + + return `-----BEGIN CERTIFICATE----- +${wrapped} +-----END CERTIFICATE----- +` +} + +export async function writeMappedKeys(keys: Map>) { + for (let [cert, paths] of keys.entries()) { + let serialized = serializeCert(cert) + for (let path of paths) { + console.log({ + path: path, + cert: serialized, + }) + await fs.writeFile(path, serialized) + } + } +}