Last active
May 18, 2025 20:33
-
-
Save ThePedroo/b04f209696953b9a144455fb12b90265 to your computer and use it in GitHub Desktop.
SignSee, a tool to check if a ReZygisk build has been tampered with and if it uses official public key.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env node | |
| /* | |
| SignSee 0.0.2 by ThePedroo | |
| Licensed under AGPL 3.0 by ThePedroo, | |
| read about in Open Source Initiative: https://opensource.org/license/agpl-v3 | |
| */ | |
| import fs from 'node:fs' | |
| import path from 'node:path' | |
| import crypto from 'node:crypto' | |
| import * as ed from '@noble/ed25519' | |
| /* INFO: Polyfill for etc.sha512Sync */ | |
| ed.etc.sha512Sync = (...m) => crypto.createHash('sha512').update(ed.etc.concatBytes(...m)).digest() | |
| const SIGNATURE_LENGTH = 64 | |
| const PUBLIC_KEY_LENGTH = 32 | |
| /* INFO: "Magic" string, created based on PerformanC's private key */ | |
| const PERFORMANC_PUBLIC_KEY = 'ddf4a98c0f7b121f3e1281a7de957ed3e5b528aaf03b13e7f04a561054d9187f' | |
| /* INFO: Misaki structure | |
| <digest/signature> + <public key> | |
| <public key> = 32 bytes | |
| <digest/signature> = 64 bytes | |
| The digest of the entire project is based on both filename, file content size | |
| and file content. We create a buffer with all of those datas, in an alphabetical order, | |
| then ed25519 algorithm with SHA512 is used to verify if the digest in misaki file | |
| is valid using the information from the files in the folder, made by SignSee. | |
| OBS: The path to the file is not included in the digest, only the filename. | |
| */ | |
| async function signFiles(files) { | |
| let toSign = Buffer.alloc(0) | |
| for await (const file of files) { | |
| const fileNameBytes = Buffer.from(path.basename(file), 'utf-8') | |
| toSign = Buffer.concat([ toSign, fileNameBytes ]) | |
| toSign = Buffer.concat([ toSign, Buffer.from([0]) ]) | |
| const stats = fs.statSync(file) | |
| const sizeBuffer = Buffer.alloc(8) | |
| sizeBuffer.writeBigInt64LE(BigInt(stats.size), 0) | |
| toSign = Buffer.concat([ toSign, sizeBuffer ]) | |
| const stream = fs.createReadStream(file) | |
| for await (const chunk of stream) { | |
| toSign = Buffer.concat([ toSign, chunk ]) | |
| } | |
| } | |
| return toSign | |
| } | |
| async function recursiveReadDir(dir) { | |
| const files = [] | |
| for await (const file of fs.readdirSync(dir)) { | |
| const fullPath = path.join(dir, file) | |
| if (fs.statSync(fullPath).isDirectory()) { | |
| files.push(...await recursiveReadDir(fullPath)) | |
| } else { | |
| files.push(fullPath) | |
| } | |
| } | |
| return files | |
| } | |
| async function verifyMisaki(root) { | |
| let verification = null | |
| let publicKey = null | |
| { | |
| const misakiBuffer = fs.readFileSync(`${root}/misaki.sig`) | |
| const signature = misakiBuffer.subarray(0, SIGNATURE_LENGTH) | |
| publicKey = misakiBuffer.subarray(SIGNATURE_LENGTH) | |
| if (publicKey.length === 0) throw new Error('Empty public key') | |
| if (publicKey.length !== PUBLIC_KEY_LENGTH) throw new Error('Invalid public key length') | |
| let allFiles = await recursiveReadDir(root) | |
| /* INFO: Alphabetical order of files is important, as the digest is based on the order of the files */ | |
| allFiles.sort() | |
| /* INFO: misaki.sig file must be ignored for the digest part */ | |
| allFiles = allFiles.filter((file) => file !== `${root}/misaki.sig`) | |
| const signedData = await signFiles(allFiles) | |
| verification = ed.verify(signature, signedData, publicKey) | |
| } | |
| return { | |
| IsNotTampered: verification, | |
| isOfficial: publicKey.equals(Buffer.from(PERFORMANC_PUBLIC_KEY, 'hex')) | |
| } | |
| } | |
| (async () => { | |
| console.log('SignSee 0.0.2 by ThePedroo') | |
| console.log('Licensed under AGPL 3.0 by ThePedroo') | |
| console.log('Read about in Open Source Initiative: https://opensource.org/license/agpl-v3') | |
| console.log() | |
| if (process.argv.length < 3) { | |
| console.error('Usage: node verify-zip.js <path-to-unzipped-folder>') | |
| process.exit(1) | |
| } | |
| if (!fs.existsSync(process.argv[2])) { | |
| console.error('Invalid path to unzipped folder') | |
| process.exit(1) | |
| } | |
| if (!fs.statSync(process.argv[2]).isDirectory()) { | |
| console.error('Invalid path to unzipped folder, not a directory') | |
| process.exit(1) | |
| } | |
| if (!fs.existsSync(`${process.argv[2]}/misaki.sig`)) { | |
| console.error('Invalid path to unzipped folder, missing misaki.sig') | |
| console.error('The build is either old or has been tampered with. Do not trust.') | |
| process.exit(1) | |
| } | |
| if (!fs.statSync(`${process.argv[2]}/misaki.sig`).size === SIGNATURE_LENGTH + PUBLIC_KEY_LENGTH) { | |
| console.error('Invalid misaki.sig file, invalid size') | |
| console.error('The build is either old or has been tampered with. Do not trust.') | |
| process.exit(1) | |
| } | |
| const timeInit = Date.now() | |
| const buildIntegrity = await verifyMisaki(process.argv[2]) | |
| console.log(`* Time taken to verify: ${Date.now() - timeInit}ms`) | |
| console.log() | |
| console.log('Code Integrity Check:') | |
| console.log(` - Build Integrity: ${buildIntegrity.IsNotTampered ? 'OK' : 'Tampered'}`) | |
| console.log(` - Is Official: ${buildIntegrity.isOfficial ? 'Yes' : 'No'}`) | |
| console.log() | |
| console.log() | |
| console.log('OBS: Official means that the zip contains the public key generated by PerformanC members\'s\n' + | |
| 'private key to create the build, certifying that the build is not malicious.') | |
| console.log() | |
| console.log('OBS: Is Official must be taken into consideration together with Build Integrity.\n' + | |
| 'If Build Integrity is OK but Is Official is not, it means that the build was made and signed\n' + | |
| 'by someone else and MAY be malicious.\n' + | |
| 'If Build Integrity is not OK, it means that the build was tampered with and something, malicious or\n' + | |
| 'not, was modified. Those shouldn\'t be used without extreme care.') | |
| })() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment