@honor-minigame/cli
Version:
honor minigame pack cli
680 lines (586 loc) • 15.5 kB
JavaScript
/*
* Copyright (c) 2021-present, the hapjs-platform Project Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import crypto from "crypto"
import rs from "jsrsasign"
import Base64 from "./base64.js"
import { digest } from "./digest.js"
import {
getRemoteCryptoSignFunction,
defaultCryptoSignFunction,
} from "./signer.js"
function unZipFiles(fileBuf, files) {
// 读取zip文件
if (!fileBuf || fileBuf.length <= 4) {
console.error("### HONOR PACK ### Zip文件打开失败")
return false
}
// 检查文件类型是否正确
const filemagic = fileBuf.readInt32LE(0)
if (filemagic !== 0x4034b50) {
console.error("### HONOR PACK ### Zip文件格式错误")
return false
}
// 解析数据块
const chunks = parserZip(fileBuf)
chunks.options = { files }
if (chunks.tag) {
// 解析成功, 生成签名块
// 分别处理3个块签名
Object.keys(chunks.sections).forEach((item) => {
const chunk = chunks.sections[item]
processChunk(fileBuf, chunk)
})
return [chunks, fileBuf]
}
return false
}
/**
* 为 zip buffer 签名,获得签名后的 buffer
*
* @param fileBuf ZIP文件的内容
* @param files 文件列表,每个元素拥有:name, hash
* @param prikey
* @param certpem
*/
function doSign(fileBuf, files, prikey, certpem) {
const result = unZipFiles(fileBuf, files)
if (!result) {
return false
}
const [chunks] = result
// 生成整体签名
signChunk(chunks, prikey, certpem)
// 写入zip文件
const signContent = saveChunk(fileBuf, chunks)
return signContent
}
/**
* 解析Zip, 分解数据块
* @param buf
*/
function parserZip(buf) {
const chunk = {
tag: false,
length: buf.length,
sections: {
header: null,
central: null,
footer: null,
},
}
chunk.sections.footer = readEOCD(buf) // 至少22个字节
if (chunk.sections.footer.tag) {
chunk.sections.central = readCD(
buf,
chunk.sections.footer.previous,
chunk.sections.footer.startIndex - chunk.sections.footer.previous
)
if (chunk.sections.central.tag) {
chunk.sections.header = readFH(
buf,
chunk.sections.central.previous,
chunk.sections.central.startIndex - chunk.sections.central.previous
)
if (chunk.sections.header.tag) {
chunk.tag = true
}
}
}
return chunk
}
/**
* 从后往前读取
* @param buf
*/
function readEOCD(buf) {
const chunk = {
tag: false,
}
if (buf && buf.length >= 22) {
let offset = buf.length - 22
let tag
// 从开始位置往前单个字节读取, 检查
while (offset >= 0) {
tag = buf.readInt32LE(offset)
if (tag === 0x6054b50) {
// 如果找到起始位置
chunk.tag = true
chunk.startIndex = offset
chunk.len = buf.length - offset
chunk.previous = buf.readInt32LE(offset + 16)
break
}
offset -= 1
}
}
return chunk
}
/**
* 从后往前读取
* @param buf
* @param offset
* @param size
*/
function readCD(buf, offset, size) {
const chunk = {
tag: false,
}
if (buf && buf.length >= offset) {
const tag = buf.readInt32LE(offset)
if (tag === 0x2014b50) {
// 如果找到起始位置
chunk.tag = true
chunk.startIndex = offset
chunk.len = size
chunk.previous = buf.readInt32LE(offset + 42)
}
}
return chunk
}
/**
* 从后往前读取
* @param buf
* @param offset
* @param size
*/
function readFH(buf, offset, size) {
const chunk = {
tag: false,
}
if (buf && buf.length >= offset) {
const tag = buf.readInt32LE(offset)
if (tag === 0x4034b50) {
// 如果找到起始位置
chunk.tag = true
chunk.startIndex = offset
chunk.len = size
chunk.previous = -1
}
}
return chunk
}
/**
* 数据块hash
* @param buf
* @param chunk
*/
function processChunk(buf, chunk) {
// 存储每个块的摘要
const cur = chunk.startIndex
const end = chunk.startIndex + chunk.len
const chk = buf.slice(cur, end)
const header = Buffer.alloc(5 + chunk.len)
header[0] = 0xa5
header.writeInt32LE(chk.length, 1)
chk.copy(header, 5)
const signer = crypto.createHash("SHA256")
signer.update(header)
chunk.sign = signer.digest()
}
/**
* 对整个chunk签名
* @param chunks
* @param prikey
* @param certpem
*/
function signChunk(chunks, prikey, certpem) {
const sections = chunks.sections
// 二进制拼接每个块摘要
const length =
sections.header.sign.length +
sections.central.sign.length +
sections.footer.sign.length +
5
const wholedata = Buffer.alloc(length)
let offset = 0
wholedata.writeInt8(0x5a, 0)
wholedata.writeInt32LE(3, 1)
offset += 5
function writeBuffer(buf) {
buf.copy(wholedata, offset)
offset += buf.length
}
writeBuffer(sections.header.sign)
writeBuffer(sections.central.sign)
writeBuffer(sections.footer.sign)
// 计算整体摘要
const signer = crypto.createHash("SHA256")
signer.update(wholedata)
const signature = signer.digest()
// 生成sign block, 计算block总长度, 向buf中考入数据
const signchunk = makeSignChunk(chunks.options, signature, prikey, certpem)
chunks.signchunk = saveSignChunk(signchunk)
}
/**
*
* @param options
* @param sign
* @param prikey
* @param certpem
*/
function makeSignChunk(options, sign, prikey, certpem) {
// 提取公钥
const cert = Buffer.from(Base64.unarmor(certpem))
const c = new rs.X509()
c.readCertPEM(certpem.toString())
const pubkey = rs.KEYUTIL.getPEM(c.subjectPublicKeyRSA)
// 摘要块
const digestBuf = Buffer.alloc(sign.length + 12)
digestBuf.writeInt32LE(sign.length + 8, 0)
digestBuf.writeInt32LE(0x0103, 4)
digestBuf.writeInt32LE(sign.length, 8)
sign.copy(digestBuf, 12)
const digestBlock = {
len: digestBuf.length,
buffer: digestBuf,
}
// 证书块
const certBuf = Buffer.alloc(cert.length + 4)
certBuf.writeInt32LE(cert.length, 0)
cert.copy(certBuf, 4)
const certBlock = {
len: certBuf.length,
buffer: certBuf,
}
// 签名数据
const signdataBlock = {
len: 12,
digests: {
size: 0,
data: [],
},
certs: {
size: 0,
data: [],
},
additional: 0,
}
signdataBlock.digests.data.push(digestBlock)
signdataBlock.digests.size += digestBlock.len
signdataBlock.len += digestBlock.len
signdataBlock.certs.data.push(certBlock)
signdataBlock.certs.size += certBlock.len
signdataBlock.len += certBlock.len
// 将public.pem转化为der
const pubbuf = Buffer.from(Base64.unarmor(pubkey))
const signBlock = {
len: 16 + pubbuf.length,
size: 12 + pubbuf.length,
signdata: {
size: 0,
buffer: null,
},
signatures: {
size: 0,
data: [],
},
pubkey: {
size: pubbuf.length,
buffer: pubbuf,
},
}
signBlock.signdata.buffer = makeSignDataBuffer(signdataBlock)
signBlock.signdata.size = signdataBlock.len
signBlock.size += signdataBlock.len
signBlock.len += signdataBlock.len
// 生成签名
const signature = callCryptoSignFunction(
signBlock.signdata.buffer,
prikey,
certpem
)
const signatureBlock = {
len: signature.length + 12,
size: signature.length + 8,
id: 0x0103,
buffer: signature,
}
signBlock.signatures.data.push(signatureBlock)
signBlock.signatures.size += signatureBlock.len
signBlock.size += signatureBlock.len
signBlock.len += signatureBlock.len
const signBlocks = {
len: 4,
size: 0,
data: [],
}
signBlocks.data.push(signBlock)
signBlocks.size += signBlock.len
signBlocks.len += signBlock.len
// 生成key-value
const kvBlock = {
len: signBlocks.len + 12,
size: signBlocks.len + 4,
id: 0x01000101,
value: signBlocks,
}
const signchunk = {
len: 32,
size: 24,
data: [],
}
signchunk.data.push(kvBlock)
signchunk.size += kvBlock.len
signchunk.len += kvBlock.len
// 添加文件列表hash kvblock
if (options.files) {
const filehashChunk = signFiles(options.files, prikey, certpem)
if (filehashChunk) {
const filesignBlocks = {
len: 4,
size: 0,
data: [],
}
filesignBlocks.data.push(filehashChunk)
filesignBlocks.size += filehashChunk.length
filesignBlocks.len += filehashChunk.length
const filekvBlock = {
len: filesignBlocks.len + 12,
size: filesignBlocks.len + 4,
id: 0x01000201,
value: filesignBlocks,
}
signchunk.data.push(filekvBlock)
signchunk.size += filekvBlock.len
signchunk.len += filekvBlock.len
}
}
return signchunk
}
/**
*
* @param block
*/
function makeSignDataBuffer(block) {
const buffer = Buffer.alloc(block.len)
let offset = 0
buffer.writeInt32LE(block.digests.size, offset)
offset += 4
block.digests.data.forEach((item) => {
item.buffer.copy(buffer, offset)
offset += item.len
})
buffer.writeInt32LE(block.certs.size, offset)
offset += 4
block.certs.data.forEach((item) => {
item.buffer.copy(buffer, offset)
offset += item.len
})
buffer.writeInt32LE(block.additional, offset)
return buffer
}
const SigMagic = "RPK Sig Block 42"
/**
*
* @param signchunk
*/
function saveSignChunk(signchunk) {
const buffer = Buffer.alloc(signchunk.len)
let offset = 0
// 大小
buffer.writeInt32LE(signchunk.size, offset)
offset += 4
buffer.writeInt32LE(0, offset)
offset += 4
// key-value
signchunk.data.forEach((kv) => {
buffer.writeInt32LE(kv.size, offset)
offset += 4
buffer.writeInt32LE(0, offset)
offset += 4
buffer.writeInt32LE(kv.id, offset)
offset += 4
// value
buffer.writeInt32LE(kv.value.size, offset)
offset += 4
if (kv.id === 0x01000101) {
// sign blocks
kv.value.data.forEach((block) => {
buffer.writeInt32LE(block.size, offset)
offset += 4
// signdata
buffer.writeInt32LE(block.signdata.size, offset)
offset += 4
block.signdata.buffer.copy(buffer, offset)
offset += block.signdata.buffer.length
// signature
buffer.writeInt32LE(block.signatures.size, offset)
offset += 4
block.signatures.data.forEach((signature) => {
buffer.writeInt32LE(signature.size, offset)
offset += 4
buffer.writeInt32LE(signature.id, offset)
offset += 4
buffer.writeInt32LE(signature.buffer.length, offset)
offset += 4
signature.buffer.copy(buffer, offset)
offset += signature.buffer.length
})
// pubkey
buffer.writeInt32LE(block.pubkey.size, offset)
offset += 4
block.pubkey.buffer.copy(buffer, offset)
offset += block.pubkey.buffer.length
})
} else if (kv.id === 0x01000201) {
// files blocks
kv.value.data.forEach((block) => {
block.copy(buffer, offset)
offset += block.length
})
}
})
// 大小
buffer.writeInt32LE(signchunk.size, offset)
offset += 4
buffer.writeInt32LE(0, offset)
offset += 4
// 魔法值
const magic = Buffer.from(SigMagic)
magic.copy(buffer, offset)
return buffer
}
/**
* 为 buffer 添加签名文件
* @param buf
* @param chunks
*/
function saveChunk(buf, chunks) {
// 创建新buffer
const newBuffer = Buffer.alloc(buf.length + chunks.signchunk.length)
let offset = 0
const sections = chunks.sections
// 拷贝header
buf.copy(
newBuffer,
offset,
sections.header.startIndex,
sections.header.startIndex + sections.header.len
)
offset += sections.header.len
// 拷贝signblock
chunks.signchunk.copy(newBuffer, offset)
offset += chunks.signchunk.length
// 拷贝central
buf.copy(
newBuffer,
offset,
sections.central.startIndex,
sections.central.startIndex + sections.central.len
)
offset += sections.central.len
// 修改eocd
buf.writeInt32LE(
sections.central.startIndex + chunks.signchunk.length,
sections.footer.startIndex + 16
)
// 拷贝eocd
buf.copy(
newBuffer,
offset,
sections.footer.startIndex,
sections.footer.startIndex + sections.footer.len
)
offset += sections.footer.len
return newBuffer
}
/**
* 加签名文件
* @param filehashs
* @param prikey
* @param certpem
*/
function signFiles(filehashs, prikey, certpem) {
const chunk = {
len: 8,
size: 4,
digests: [],
sign: null,
}
// 生成hash块
filehashs.forEach((item) => {
// name hash
const namehash = digest(item.name)
// 计算大小
const sum = 6 + item.hash.length
const chk = Buffer.alloc(sum)
let offset = 0
chk.writeInt32LE(namehash, offset)
offset += 4
chk.writeInt16LE(item.hash.length, offset)
offset += 2
item.hash.copy(chk, offset)
offset += item.hash.length
chunk.digests.push(chk)
chunk.size += sum
chunk.len += sum
})
// 生成整体签名
signDigestChunk(chunk, prikey, certpem)
// 写入文件
return saveDigestChunk(chunk)
}
/**
*
* @param chunk
* @param prikey
* @param certpem
*/
function signDigestChunk(chunk, prikey, certpem) {
const buf = Buffer.alloc(chunk.size)
let offset = 0
buf.writeInt32LE(0x0103, offset)
offset += 4
chunk.digests.forEach((chk) => {
chk.copy(buf, offset)
offset += chk.length
})
chunk.digests = buf.slice()
// 生成签名
const signature = callCryptoSignFunction(buf, prikey, certpem)
chunk.sign = {
len: 12 + signature.length,
size: 8 + signature.length,
id: 0x0103,
data: signature,
}
chunk.len += chunk.sign.len
}
/**
* @param chunk
*/
function saveDigestChunk(chunk) {
// 创建新buffer
const newBuffer = Buffer.alloc(chunk.len)
let offset = 0
newBuffer.writeInt32LE(chunk.size, offset)
offset += 4
// 文件hash列表
chunk.digests.copy(newBuffer, offset)
offset += chunk.digests.length
// 写入签名
newBuffer.writeInt32LE(chunk.sign.size, offset)
offset += 4
newBuffer.writeInt32LE(chunk.sign.id, offset)
offset += 4
newBuffer.writeInt32LE(chunk.sign.data.length, offset)
offset += 4
chunk.sign.data.copy(newBuffer, offset)
offset += chunk.sign.data.length
return newBuffer
}
function callCryptoSignFunction(buffer, prikey, certpem) {
let signature = null
if (!getRemoteCryptoSignFunction()) {
// 使用默认
signature = defaultCryptoSignFunction(buffer, prikey)
} else {
// 使用外部:传递原文件内容、证书内容
const remoteCryptoSignFunction = getRemoteCryptoSignFunction()
signature = remoteCryptoSignFunction(buffer, certpem)
}
return signature
}
export { doSign }