Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
456919df43 | ||
|
|
52f792f00f |
No files matched your search
@@ -53,6 +53,44 @@ npm run android:sync --workspaces=false
|
||||
üretim imzası içermez ve kurulabilir sürüm olarak paylaşılmamalıdır. Özel üretim
|
||||
imza anahtarı oluşturulmadı; mağaza hesabı veya ücretli hizmet açılmadı.
|
||||
|
||||
Yayın adayı için sürüm ve Android sürüm kodu birlikte açıkça verilir:
|
||||
|
||||
```powershell
|
||||
.\android\gradlew.bat -p android assembleRelease '-PaiturkReleaseVersion=0.1.4-beta.1' '-PaiturkReleaseVersionCode=6'
|
||||
```
|
||||
|
||||
Bu komut yine **imzasız** APK üretir; üretim imzasını veya cihaz kabulünü atlamaz.
|
||||
İki parametreden yalnız biri, `-dev` etiketi veya 5 ve daha küçük sürüm kodu
|
||||
verilirse derleme durur. Parametresiz geliştirici derlemesi `0.1.4-dev` / 5 kalır.
|
||||
Sonraki yayınlarda sürüm kodunu son dağıtılmış APK'dan daha büyük seçin; buradaki
|
||||
alt sınır yalnız mevcut geliştirme sürümünü korur, uzak yayın envanterini sorgulamaz.
|
||||
|
||||
12 Eylül 2026: gerçek Gradle `assembleRelease` çalışması bu parametrelerle 55
|
||||
saniyede tamamlandı; `aapt2` uygulama kimliği, `0.1.4-beta.1` / 6 ve debug
|
||||
bayrağının olmadığını doğruladı. Test çıktısının SHA-256 değeri
|
||||
`a93b93b3080ec484cc998ef316528ff7b947fa366b2ff5de375386acc66fffc4`.
|
||||
`apksigner verify` imzasız çıktıyı reddetti (beklenen sonuç). Ayrı gerçek Gradle
|
||||
çalışmaları eksik sürüm kodunu, `0.1.4-dev` yayın etiketini ve sürüm kodu 5'i
|
||||
reddetti. Bu çıktı yayımlanmadı; imza ve cihaz kabulü değildir.
|
||||
|
||||
### Dağıtım APK'sının doğrulanması
|
||||
|
||||
`scripts/verify-android-release.mjs` dağıtım öncesi salt okunur APK kontrolüdür.
|
||||
`--apk`, `--java`, `--apksigner` (Android SDK içindeki apksigner.jar), `--aapt2`,
|
||||
`--certificate` (bağımsız olarak belirlenen üretim sertifikası SHA-256 değeri),
|
||||
`--version` ve `--version-code` parametrelerinin tamamını ister. Sertifika pinini
|
||||
kontrol edilen APK'dan otomatik türetmeyin. Bu araç anahtar üretmez veya APK imzalamaz.
|
||||
|
||||
Gerçek apksigner doğrulaması, v2/v3 imza, tek beklenen sertifika, uygulama kimliği
|
||||
`online.turkservis.aiturk.mobile`, tam sürüm eşleşmesi ve debug bayrağı kontrol edilir.
|
||||
Dosya doğrulama boyunca değişirse sonuç reddedilir. Debug sertifikası ve `-dev`
|
||||
sürümü dağıtım için kabul edilmez. Başarılı rapor APK hash'ini verir fakat açıkça
|
||||
`deviceAcceptanceVerified: false` içerir; fiziksel telefon kabulünün yerine geçmez.
|
||||
|
||||
12 Eylül 2026: mevcut mutual-identity geliştirici APK'sı gerçek SDK araçlarıyla
|
||||
kontrol edildi ve beklenildiği gibi `debug-signing-certificate` ile reddedildi.
|
||||
Üretim sertifikalı pozitif APK doğrulaması henüz yapılmadı.
|
||||
|
||||
## Uyumlu IDE ve bağlantı servisi
|
||||
|
||||
Bu kaynak ağacındaki masaüstü paketini normal masaüstü build süreciyle derleyin.
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
// Release identity is explicit; ordinary developer builds retain the -dev label.
|
||||
def releaseVersion = providers.gradleProperty('aiturkReleaseVersion').orNull
|
||||
def releaseCode = providers.gradleProperty('aiturkReleaseVersionCode').orNull
|
||||
if ((releaseVersion == null) != (releaseCode == null)) {
|
||||
throw new GradleException('Both aiturkReleaseVersion and aiturkReleaseVersionCode are required')
|
||||
}
|
||||
def selectedVersionCode = 5
|
||||
if (releaseVersion != null) {
|
||||
if (!(releaseVersion ==~ /\d+\.\d+\.\d+(?:-beta\.\d+)?/)) {
|
||||
throw new GradleException('Release version must be X.Y.Z or X.Y.Z-beta.N; -dev is not a release')
|
||||
}
|
||||
if (!(releaseCode ==~ /[1-9]\d{0,9}/) || releaseCode.toLong() <= 5 || releaseCode.toLong() > 2100000000L) {
|
||||
throw new GradleException('Release version code must be greater than 5 and at most 2100000000')
|
||||
}
|
||||
selectedVersionCode = releaseCode.toInteger()
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "online.turkservis.aiturk.mobile"
|
||||
compileSdk = rootProject.ext.compileSdkVersion
|
||||
@@ -7,8 +24,8 @@ android {
|
||||
applicationId "online.turkservis.aiturk.mobile"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 5
|
||||
versionName "0.1.4-dev"
|
||||
versionCode selectedVersionCode
|
||||
versionName releaseVersion ?: "0.1.4-dev"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { createReadStream } from 'node:fs'
|
||||
import { lstat } from 'node:fs/promises'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { parseArgs } from 'node:util'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
|
||||
export function validateAndroidRelease({ signature, badging, certificate, version, versionCode }) {
|
||||
const expected = certificate.trim().replaceAll(':', '').toLowerCase()
|
||||
if (!/^[a-f0-9]{64}$/.test(expected)) throw new Error('certificate-pin-invalid')
|
||||
if (!/^\d+\.\d+\.\d+(?:-beta\.\d+)?$/.test(version)) throw new Error('release-version-invalid')
|
||||
if (!Number.isSafeInteger(versionCode) || versionCode <= 0) throw new Error('version-code-invalid')
|
||||
const signers = [...signature.matchAll(/^Signer #\d+ certificate SHA-256 digest: ([a-fA-F0-9]{64})\s*$/gm)]
|
||||
if (signers.length !== 1 || signers[0][1].toLowerCase() !== expected) throw new Error('signing-certificate-mismatch')
|
||||
if (/^Signer #\d+ certificate DN:.*\bCN=Android Debug\b/im.test(signature)) throw new Error('debug-signing-certificate')
|
||||
if (!/^Verified using v[23](?:\.\d+)? scheme[^\r\n]*: true\s*$/m.test(signature)) throw new Error('modern-apk-signature-required')
|
||||
const packages = [...badging.matchAll(/^package: name='([^']+)' versionCode='([^']+)' versionName='([^']+)'/gm)]
|
||||
if (packages.length !== 1 || packages[0][1] !== 'online.turkservis.aiturk.mobile') throw new Error('application-id-mismatch')
|
||||
if (packages[0][2] !== String(versionCode) || packages[0][3] !== version) throw new Error('apk-version-mismatch')
|
||||
if (/^application-debuggable\b/m.test(badging)) throw new Error('debuggable-apk')
|
||||
return { applicationId: packages[0][1], version, versionCode, certificateSha256: expected }
|
||||
}
|
||||
|
||||
async function regularFile(path) {
|
||||
let current = path
|
||||
while (true) {
|
||||
const info = await lstat(current)
|
||||
if (info.isSymbolicLink()) throw new Error('apk-reparse-path')
|
||||
if (current === path && !info.isFile()) throw new Error('apk-not-regular')
|
||||
const parent = dirname(current)
|
||||
if (parent === current) return
|
||||
current = parent
|
||||
}
|
||||
}
|
||||
|
||||
async function digest(path) {
|
||||
const hash = createHash('sha256')
|
||||
for await (const chunk of createReadStream(path)) hash.update(chunk)
|
||||
return hash.digest('hex')
|
||||
}
|
||||
|
||||
function tool(executable, args) {
|
||||
const env = { ...process.env }
|
||||
delete env.JAVA_TOOL_OPTIONS
|
||||
delete env._JAVA_OPTIONS
|
||||
delete env.JDK_JAVA_OPTIONS
|
||||
const result = spawnSync(executable, args, { env, encoding: 'utf8', windowsHide: true, timeout: 60_000, maxBuffer: 1024 * 1024 })
|
||||
if (result.error || result.status !== 0) throw new Error('android-verification-tool-failed')
|
||||
return result.stdout
|
||||
}
|
||||
|
||||
export async function verifyAndroidRelease(options) {
|
||||
const apk = resolve(options.apk)
|
||||
await regularFile(apk)
|
||||
const before = await lstat(apk)
|
||||
const sha256 = await digest(apk)
|
||||
const signature = tool(resolve(options.java), ['-jar', resolve(options.apksigner), 'verify', '--verbose', '--print-certs', apk])
|
||||
const badging = tool(resolve(options.aapt2), ['dump', 'badging', apk])
|
||||
const identity = validateAndroidRelease({ ...options, signature, badging })
|
||||
await regularFile(apk)
|
||||
const after = await lstat(apk)
|
||||
if (before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs || await digest(apk) !== sha256) throw new Error('apk-changed-during-verification')
|
||||
return { verified: true, ...identity, size: after.size, sha256, deviceAcceptanceVerified: false }
|
||||
}
|
||||
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
||||
const { values } = parseArgs({ options: Object.fromEntries(['apk', 'java', 'apksigner', 'aapt2', 'certificate', 'version', 'version-code'].map(name => [name, { type: 'string' }])) })
|
||||
try {
|
||||
if (Object.keys(values).length !== 7) throw new Error('all-release-verification-arguments-required')
|
||||
console.log(JSON.stringify(await verifyAndroidRelease({ ...values, versionCode: Number(values['version-code']) })))
|
||||
} catch (error) {
|
||||
const code = /^[a-z][a-z0-9-]{1,95}$/.test(error.message) ? error.message : 'android-release-verification-failed'
|
||||
console.error(JSON.stringify({ verified: false, code }))
|
||||
process.exitCode = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import { validateAndroidRelease } from '../scripts/verify-android-release.mjs'
|
||||
|
||||
const certificate = 'a'.repeat(64)
|
||||
const fixture = {
|
||||
certificate, version: '0.1.4-beta.1', versionCode: 6,
|
||||
signature: `Verified using v2 scheme (APK Signature Scheme v2): true\nSigner #1 certificate DN: CN=AITURK Release\nSigner #1 certificate SHA-256 digest: ${certificate}\n`,
|
||||
badging: "package: name='online.turkservis.aiturk.mobile' versionCode='6' versionName='0.1.4-beta.1'\n",
|
||||
}
|
||||
|
||||
test('APK release identity requires a pinned certificate and exact version', () => {
|
||||
assert.equal(validateAndroidRelease(fixture).certificateSha256, certificate)
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, certificate: 'b'.repeat(64) }), /certificate-mismatch/)
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, versionCode: 7 }), /version-mismatch/)
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, version: '0.1.4-dev' }), /release-version-invalid/)
|
||||
})
|
||||
|
||||
test('debug and ambiguous APK identities are never release-ready', () => {
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, signature: fixture.signature.replace('AITURK Release', 'Android Debug') }), /debug-signing/)
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, badging: fixture.badging + 'application-debuggable\n' }), /debuggable-apk/)
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, badging: fixture.badging.replace('online.turkservis.aiturk.mobile', 'other.application') }), /application-id/)
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, signature: fixture.signature + `Signer #2 certificate SHA-256 digest: ${certificate}\n` }), /certificate-mismatch/)
|
||||
assert.throws(() => validateAndroidRelease({ ...fixture, signature: fixture.signature.replace(': true', ': false') }), /modern-apk-signature/)
|
||||
})
|
||||
Reference in new issue
Block a user