selinux: Add support for parsing mac_permissions.xml and keys.conf

This commit is contained in:
Danny Lin 2021-11-14 03:26:07 -08:00
parent fe09189d55
commit 3961bde95f

166
src/sepolicy/keys.ts Normal file
View file

@ -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<string, string>
}
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<string, string> | 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<string, string>()
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<KeyInfo>, srcMacPerms: Array<MacSigner>, compiledMacPerms: Array<MacSigner>) {
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<Uint8Array, Iterable<string>>) {
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)
}
}
}