XuqmGroup-RNSDK/packages/common/src/internalSecurity.ts

69 行
1.9 KiB
TypeScript

import { ed25519 } from '@noble/curves/ed25519.js'
import { utf8ToBytes } from '@noble/hashes/utils.js'
import { decodeBase64 } from '../security/base64.js'
import { canonicalJson } from '../security/canonical-json.js'
const TRUSTED_SIGNING_KEYS = require('../security/trusted-signing-keys.json') as Readonly<
Record<string, string>
>
export interface SignedPluginManifest {
appKey: string
packageName: string
platform: 'ANDROID' | 'IOS'
moduleId: string
type: 'common' | 'app' | 'buz'
version: string
appVersionRange: string
builtAgainstNativeBaselineId: string
buildId: string
bundleFormat: 'hermes-bytecode' | 'javascript'
commonVersionRange?: string
minNativeApiLevel: number
bundleSha256: string
archiveSha256: string
}
export class PluginManifestSignatureError extends Error {
readonly name = 'PluginManifestSignatureError'
constructor(
message: string,
public readonly code: 'UNKNOWN_SIGNING_KEY' | 'INVALID_SIGNATURE',
) {
super(message)
}
}
/** @internal 插件候选必须在生成计划和下载前通过同一可信密钥集合验签。 */
export function verifySignedPluginManifest(
manifest: SignedPluginManifest,
keyId: string,
signature: string,
trustedKeys: Readonly<Record<string, string>> = TRUSTED_SIGNING_KEYS,
): void {
const publicKey = trustedKeys[keyId]
if (!publicKey) {
throw new PluginManifestSignatureError(
`[XuqmSecurity] Untrusted plugin signing key: ${keyId}`,
'UNKNOWN_SIGNING_KEY',
)
}
let valid = false
try {
valid = ed25519.verify(
decodeBase64(signature),
utf8ToBytes(canonicalJson(manifest)),
decodeBase64(publicKey),
)
} catch {
valid = false
}
if (!valid) {
throw new PluginManifestSignatureError(
`[XuqmSecurity] Invalid plugin manifest signature: ${manifest.moduleId}@${manifest.version}`,
'INVALID_SIGNATURE',
)
}
}