import { readFile } from 'node:fs/promises' import { resolve } from 'node:path' const PACKAGE_NAME = 'com.d3ro.voice' const REQUIRED_RELATION = 'delegate_permission/common.handle_all_urls' const LIVE_URL = 'https://d3ro.chanpaca.net/.well-known/assetlinks.json' const COMPROMISED_SIGNER_SHA256 = '06eec757722ee7cd3dfbc53202d974aaf0417a7d397f9ef1e5611088ebb2e481' const SOURCE_PATHS = [ 'site/public/.well-known/assetlinks.json', 'apps/web/public/.well-known/assetlinks.json', 'apps/api-server/wwwroot/.well-known/assetlinks.json', ] const releaseIdentity = JSON.parse( await readFile(resolve('release/android-release-identity.json'), 'utf8'), ) function fail(message) { throw new Error(`android_app_links_invalid:${message}`) } function normalizeFingerprint(value) { if (typeof value !== 'string') fail('certificate_fingerprint_not_string') const normalized = value.replace(/:/g, '').toUpperCase() if (!/^[0-9A-F]{64}$/.test(normalized)) fail('certificate_fingerprint_format') return normalized } function rejectCompromisedFingerprint(value, label) { const normalized = normalizeFingerprint(value) if (normalized === COMPROMISED_SIGNER_SHA256.toUpperCase()) { fail(`${label}_compromised_certificate`) } return normalized } function parseArguments(argv) { const options = {} for (let index = 0; index < argv.length; index += 1) { const current = argv[index] if (!current.startsWith('--')) fail(`unexpected_argument_${current}`) const value = argv[index + 1] if (!value || value.startsWith('--')) fail(`missing_value_${current.slice(2)}`) options[current.slice(2)] = value index += 1 } const allowed = new Set([ 'expected-play-app-signing-cert-sha256', 'forbidden-upload-cert-sha256', 'live-url', ]) for (const name of Object.keys(options)) { if (!allowed.has(name)) fail(`unexpected_argument_${name}`) } return options } function fingerprintsFromDocument(document, label) { if (!Array.isArray(document)) fail(`${label}_root_not_array`) const matchingEntries = document.filter((entry) => ( entry?.target?.namespace === 'android_app' && entry?.target?.package_name === PACKAGE_NAME )) if (matchingEntries.length !== 1) fail(`${label}_package_entry_count_${matchingEntries.length}`) const entry = matchingEntries[0] if (!Array.isArray(entry.relation) || !entry.relation.includes(REQUIRED_RELATION)) { fail(`${label}_required_relation_missing`) } if (!Array.isArray(entry.target.sha256_cert_fingerprints)) { fail(`${label}_fingerprints_missing`) } const fingerprints = new Set(entry.target.sha256_cert_fingerprints.map((fingerprint) => ( rejectCompromisedFingerprint(fingerprint, label) ))) if (fingerprints.size === 0) fail(`${label}_fingerprints_empty`) if (fingerprints.size !== entry.target.sha256_cert_fingerprints.length) { fail(`${label}_duplicate_fingerprints`) } return [...fingerprints].sort() } function rejectUploadCertificateAssociation(fingerprints, uploadFingerprint, label) { if (uploadFingerprint && fingerprints.includes(uploadFingerprint)) { fail(`${label}_contains_upload_certificate`) } } function requireExactPlayCertificate(fingerprints, expectedFingerprint, label) { if (!expectedFingerprint) return if (fingerprints.length !== 1 || fingerprints[0] !== expectedFingerprint) { fail(`${label}_play_certificate_set_mismatch`) } } function validateLiveResponseMetadata(response, requestedUrl) { if (response.status !== 200) fail(`live_http_${response.status}`) if (response.redirected) fail('live_redirected') if (new URL(response.url).href !== new URL(requestedUrl).href) { fail('live_final_url_mismatch') } const contentType = response.headers.get('content-type') ?? '' if (!/^application\/json(?:\s*;|$)/i.test(contentType)) { fail('live_content_type') } } function runSelfTest() { const safeFingerprint = '4FAC6924821C50DAABED764932A53C486F8C6C5F34B9F18DB920AA4099152B54' const safeDocument = [{ relation: [REQUIRED_RELATION], target: { namespace: 'android_app', package_name: PACKAGE_NAME, sha256_cert_fingerprints: [safeFingerprint], }, }] if (fingerprintsFromDocument(safeDocument, 'self_test')[0] !== safeFingerprint) { fail('self_test_safe_fingerprint_mismatch') } const uploadFingerprint = '7A'.repeat(32) rejectUploadCertificateAssociation([safeFingerprint], uploadFingerprint, 'self_test_safe') let uploadAssociationRejected = false try { rejectUploadCertificateAssociation([uploadFingerprint], uploadFingerprint, 'self_test') } catch (error) { uploadAssociationRejected = error instanceof Error && error.message.includes('self_test_contains_upload_certificate') } if (!uploadAssociationRejected) fail('self_test_upload_certificate_association_not_rejected') let compromisedRejected = false try { fingerprintsFromDocument([{ ...safeDocument[0], target: { ...safeDocument[0].target, sha256_cert_fingerprints: [COMPROMISED_SIGNER_SHA256], }, }], 'self_test') } catch (error) { compromisedRejected = error instanceof Error && error.message.includes('self_test_compromised_certificate') } if (!compromisedRejected) fail('self_test_compromised_certificate_not_rejected') let legacyArgumentRejected = false try { parseArguments(['--expected-cert-sha256', safeFingerprint]) } catch (error) { legacyArgumentRejected = error instanceof Error && error.message.includes('unexpected_argument_expected-cert-sha256') } if (!legacyArgumentRejected) fail('self_test_legacy_certificate_argument_not_rejected') requireExactPlayCertificate([safeFingerprint], safeFingerprint, 'self_test_safe') let extraCertificateRejected = false try { requireExactPlayCertificate([safeFingerprint, uploadFingerprint].sort(), safeFingerprint, 'self_test') } catch (error) { extraCertificateRejected = error instanceof Error && error.message.includes('self_test_play_certificate_set_mismatch') } if (!extraCertificateRejected) fail('self_test_extra_certificate_not_rejected') validateLiveResponseMetadata({ status: 200, redirected: false, url: LIVE_URL, headers: new Headers({ 'content-type': 'application/json; charset=utf-8' }), }, LIVE_URL) for (const [label, response] of [ ['redirect', { status: 302, redirected: false, url: LIVE_URL, headers: new Headers({ 'content-type': 'application/json' }), }], ['mime', { status: 200, redirected: false, url: LIVE_URL, headers: new Headers({ 'content-type': 'text/plain' }), }], ['final_url', { status: 200, redirected: true, url: `${LIVE_URL}?redirected=1`, headers: new Headers({ 'content-type': 'application/json' }), }], ]) { let rejected = false try { validateLiveResponseMetadata(response, LIVE_URL) } catch (error) { rejected = error instanceof Error && error.message.startsWith('android_app_links_invalid:') } if (!rejected) fail(`self_test_${label}_metadata_not_rejected`) } process.stdout.write(`${JSON.stringify({ ok: true, compromisedCertificateRejected: true, exactPlayCertificateRequired: true, liveHttpMetadataRequired: true, uploadCertificateAssociationRejected: true, })}\n`) } const cliArguments = process.argv.slice(2) if (cliArguments.length === 1 && cliArguments[0] === '--self-test') { runSelfTest() process.exit(0) } const options = parseArguments(cliArguments) if ( releaseIdentity.schemaVersion !== 1 || releaseIdentity.packageName !== PACKAGE_NAME || typeof releaseIdentity.playConsoleAppId !== 'string' || !/^\d+$/.test(releaseIdentity.playConsoleAppId) ) { fail('release_identity_invalid') } const canonicalExpectedFingerprint = rejectCompromisedFingerprint( releaseIdentity.playAppSigningCertificateSha256, 'release_identity_play_app_signing', ) const expectedFingerprint = options['expected-play-app-signing-cert-sha256'] ? rejectCompromisedFingerprint( options['expected-play-app-signing-cert-sha256'], 'expected_play_app_signing', ) : canonicalExpectedFingerprint if (expectedFingerprint !== canonicalExpectedFingerprint) { fail('expected_play_certificate_identity_mismatch') } const canonicalUploadFingerprint = rejectCompromisedFingerprint( releaseIdentity.uploadCertificateSha256, 'release_identity_upload', ) const forbiddenUploadFingerprint = options['forbidden-upload-cert-sha256'] ? rejectCompromisedFingerprint(options['forbidden-upload-cert-sha256'], 'upload') : canonicalUploadFingerprint if (forbiddenUploadFingerprint !== canonicalUploadFingerprint) { fail('upload_certificate_identity_mismatch') } if (expectedFingerprint && expectedFingerprint === forbiddenUploadFingerprint) { fail('play_and_upload_certificates_must_differ') } const sourceEvidence = [] for (const path of SOURCE_PATHS) { const document = JSON.parse(await readFile(resolve(path), 'utf8')) const fingerprints = fingerprintsFromDocument(document, path) rejectUploadCertificateAssociation(fingerprints, forbiddenUploadFingerprint, path) sourceEvidence.push({ path, fingerprints }) } const canonicalFingerprints = sourceEvidence[0].fingerprints for (const evidence of sourceEvidence.slice(1)) { if (JSON.stringify(evidence.fingerprints) !== JSON.stringify(canonicalFingerprints)) { fail(`source_fingerprint_drift_${evidence.path}`) } } if (expectedFingerprint && !canonicalFingerprints.includes(expectedFingerprint)) { fail('release_certificate_missing_from_source_assetlinks') } requireExactPlayCertificate(canonicalFingerprints, expectedFingerprint, 'source') const liveUrl = options['live-url'] ?? LIVE_URL const response = await fetch(liveUrl, { headers: { accept: 'application/json' }, redirect: 'manual', signal: AbortSignal.timeout(15_000), }) validateLiveResponseMetadata(response, liveUrl) let liveDocument try { liveDocument = await response.json() } catch { fail('live_invalid_json') } const liveFingerprints = fingerprintsFromDocument(liveDocument, 'live') rejectUploadCertificateAssociation(liveFingerprints, forbiddenUploadFingerprint, 'live') if (JSON.stringify(liveFingerprints) !== JSON.stringify(canonicalFingerprints)) { fail('live_source_fingerprint_drift') } if (expectedFingerprint && !liveFingerprints.includes(expectedFingerprint)) { fail('release_certificate_missing_from_live_assetlinks') } requireExactPlayCertificate(liveFingerprints, expectedFingerprint, 'live') console.log(JSON.stringify({ packageName: PACKAGE_NAME, relation: REQUIRED_RELATION, liveUrl, fingerprints: canonicalFingerprints, playConsoleAppId: releaseIdentity.playConsoleAppId, expectedPlayAppSigningCertificateVerified: true, uploadCertificateExcluded: true, sourcePaths: SOURCE_PATHS, }, null, 2))