İçeriğe geç
academia.sh

Ders 11 / 14

Uygulama İmzalama

Her yayımlanan sürüm bir imzalama anahtarıyla imzalanır ve aygıt, güncellemeyi yalnız kurulu uygulamayla aynı sertifikadan geliyorsa kurar. Ders yayımlanan sürümün aygıttaki kurulu uygulamaya kaç kopya olarak ulaştığını ve farklı anahtarla imzalanan sürümün sonucunu ölçer.

Yayın Güncelleme

Bu derste

  • İmzalama anahtarı, sertifika ve yükleme anahtarının görevlerini ayırır
  • Güncellemenin kurulması için sertifikanın eşleşmesi gerektiğini açıklar
  • Aynı sertifikayla imzalanan güncellemenin birebir ulaştığını ölçer
  • Farklı anahtarla imzalanan güncellemenin kayıp, kendinden sonra gelen sürüm varsa ezilen olduğunu ayırır
İçindekiler

Önceki derste olay, aygıttan sunucuya giden bir yenilemeydi. Bu derste yön tersine döner: geliştirici bir sürüm yayımlar ve o sürüm aygıtta kurulu uygulamaya ulaşmak ister. Olay yayımlanan sürümdür, alıcı aygıttaki kurulu uygulamadır. Aradaki kapı bir imzadır. Soru kursun sorusudur: gönderilen bu olay alıcıya kaç kopya olarak ulaştı?

// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}

Bu derste anahtar uygulamanın kimliğidir ve seq sürüm sırasıdır. Aynı uygulamanın sürümleri aynı anahtarı, artan seq değerlerini taşır.

İmzalama Anahtarı ve Sertifika

Bir uygulama paketi, imzalama anahtarıyla imzalanır. Anahtarın açık yarısını taşıyan belge sertifikadır ve paketle birlikte aygıta iner. Aygıt yeni bir sürümü kurmadan önce, onun sertifikasını kurulu uygulamanın sertifikasıyla karşılaştırır. Eşleşiyorsa güncelleme kurulur; eşleşmiyorsa reddedilir ve kurulu sürüm yerinde kalır.

Bu karşılaştırma bir kimlik sorusunun cevabıdır: yeni sürümü gönderen, kurulu uygulamayı gönderenle aynı taraf mı. Anahtar değişirse aygıt, anahtar dönüşümü gibi özel düzenler dışında, yeni sürümü eski uygulamayla aynı uygulama saymaz. Bu yüzden imzalama anahtarı, sürüm sürüm aynı kalması gereken tek şeydir.

Bazı mağaza düzenlerinde geliştirici paketi bir yükleme anahtarıyla imzalayıp mağazaya yükler; mağaza paketi kendi tuttuğu imzalama anahtarıyla yeniden imzalayıp aygıtlara dağıtır. Bu durumda aygıtın gördüğü sertifika yükleme anahtarının değil, mağazadaki imzalama anahtarının sertifikasıdır. Uygulama içinden kendi sertifikasının özetine bakılabilir.

// Platform kodu: PackageManager.getPackageInfo
val info = packageManager.getPackageInfo(
    packageName,
    PackageManager.GET_SIGNING_CERTIFICATES,
)
val signers = info.signingInfo?.apkContentsSigners ?: emptyArray()
val digest = MessageDigest.getInstance("SHA-256").digest(signers[0].toByteArray())

Bu parça platform kodudur; burada çıktı ya da özet değeri iddia etmiyoruz. İmzalamanın hangi araçla yapıldığı bu dersin konusu değildir ve yazılmaz. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

Modelde bir sürüm, olay ile onu imzalayan sertifikayı taşır. Aygıt, güvendiği tek bir sertifikayı tutar ve verifySignature işleviyle sürümün sertifikasını onunla karşılaştırır. Kuruluma geçen sürümler alıcının listesidir.

// Install.kt — aygit guvendigi sertifikayla eslesmeyen surumu kurmaz
class Release(val signal: Signal, val cert: String)

class Device(private val trusted: String) {
    fun verifySignature(release: Release): Boolean = release.cert == trusted
}

fun installUpdates(device: Device, releases: List<Release>): List<Signal> =
    releases.filter { device.verifySignature(it) }.map { it.signal }
// Probe.kt — yayimlanan surumler icin kurulan listeyi alici sayar
fun probe(sent: List<Signal>, received: List<Signal>): List<Arrival> =
    sent.mapIndexed { i, s -> Arrival("olay-${i + 1}", s, received) }

Kimlik Olarak İmza

Karşılaştırmanın neden bu kadar katı olduğunu anlamak için aygıtın neyi korumaya çalıştığına bakmak gerekir. Aygıtın elinde bir paket adı vardır ve ad herkesin yazabileceği bir dizedir. Adı aynı olan ikinci bir paket kurulu uygulamanın yerine geçebilseydi, kullanıcının verisi kimin elinde olduğu belirsiz bir uygulamaya devrolurdu. Sertifika bu boşluğu kapatır: ad kimlik iddia eder, sertifika onu kanıtlar.

Bunun iki sonucu vardır. Birincisi, güncellemenin kurulması için paketin içeriğinin doğru olması yetmez; imzanın da doğru anahtardan gelmesi gerekir. İkincisi, imza doğru olsa bile geliştirici anahtarı kaybeder ya da değiştirirse sonraki sürümler kurulu uygulamaya ulaşamaz. Aygıt bunu bir yapılandırma hatası olarak değil, farklı bir tarafın paketi olarak okur.

Modelde bu iki sonuç da Device sınıfında tek satırdır: release.cert == trusted. Model sertifikanın nasıl üretildiğini, hangi algoritmayla imzalandığını ve anahtar dönüşümü gibi düzenleri bilmez. Yalnız eşleşme vardır ya da yoktur.

Aynı Sertifika

Üç sürüm yayımlanır ve üçü de aygıtın güvendiği sertifikayla imzalıdır.

// Main.kt — ayni sertifikayla imzalanan her guncelleme kurulur
fun main() {
    val v1 = Signal("uygulama.ana", 1)
    val v2 = Signal("uygulama.ana", 2)
    val v3 = Signal("uygulama.ana", 3)
    val releases = listOf(
        Release(v1, "sertifika-a"),
        Release(v2, "sertifika-a"),
        Release(v3, "sertifika-a"),
    )
    val received = installUpdates(Device("sertifika-a"), releases)
    val arrivals = probe(listOf(v1, v2, v3), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
olay-1: anahtar=uygulama.ana | kopya=1 -> birebir
olay-2: anahtar=uygulama.ana | kopya=1 -> birebir
olay-3: anahtar=uygulama.ana | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 0
kayip: 0

Üç sürümün üçü kurulur ve her biri birer kopya ulaşır. Bu dersin teslimi: birebir. Aynı sertifikayla imzalanan güncelleme, aygıtın karşılaştırmasından geçer ve kurulu uygulamaya olduğu gibi ulaşır.

Aynı Sürüm İki Yoldan

Bir güncelleme aygıta iki yoldan gelebilir: mağazadan ve elle yüklenen bir dosyadan. İkisi de aynı sertifikayı taşıyorsa ikisi de karşılaştırmadan geçer.

// Main.kt — ayni surum iki yoldan gelirse iki kopya ulasir
fun main() {
    val v1 = Signal("uygulama.ana", 1)
    val v2 = Signal("uygulama.ana", 2)
    val releases = listOf(
        Release(v1, "sertifika-a"),
        Release(v2, "sertifika-a"),
        Release(v2, "sertifika-a"),
    )
    val received = installUpdates(Device("sertifika-a"), releases)
    val arrivals = probe(listOf(v1, v2), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
olay-1: anahtar=uygulama.ana | kopya=1 -> birebir
olay-2: anahtar=uygulama.ana | kopya=2 -> yinelenen
birebir: 1
yinelenen: 1
ezilen: 0
kayip: 0

İmza karşılaştırması bir sürümün ikinci kez gelip gelmediğine bakmaz; yalnız sertifikaya bakar. Gerçek aygıtta aynı sürümün ikinci kurulumu çoğunlukla sonuç değiştirmez, ama kural kopya sayısını sayar ve ikiyi yinelenen olarak yazar.

GD1. Model, kurulumu bir listeye ekleme olarak kurar. Platformda aynı sürümün ikinci kurulumu kurulu uygulamayı yerinde bırakabilir ya da yeniden yazabilir; model bu ayrımı yapmaz.

GD2. Model, sertifika karşılaştırmasını bir dize eşitliği olarak kurar. Platformda karşılaştırma imzalama düzenine, anahtar dönüşüm geçmişine ve birden çok imzalayana göre değişir.

Sınırlayıcı Ölçüm

Dört sürüm yayımlanır. Birinci ve üçüncü güvenilen sertifikayla imzalıdır; ikinci ve dördüncü farklı bir anahtarla imzalanmıştır.

// Main.kt — farkli anahtarla imzalanan surum reddedilir
fun main() {
    val v1 = Signal("uygulama.ana", 1)
    val v2 = Signal("uygulama.ana", 2)
    val v3 = Signal("uygulama.ana", 3)
    val v4 = Signal("uygulama.ana", 4)
    val releases = listOf(
        Release(v1, "sertifika-a"),
        Release(v2, "sertifika-b"),
        Release(v3, "sertifika-a"),
        Release(v4, "sertifika-b"),
    )
    val received = installUpdates(Device("sertifika-a"), releases)
    val arrivals = probe(listOf(v1, v2, v3, v4), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
olay-1: anahtar=uygulama.ana | kopya=1 -> birebir
olay-2: anahtar=uygulama.ana | kopya=0 -> ezilen
olay-3: anahtar=uygulama.ana | kopya=1 -> birebir
olay-4: anahtar=uygulama.ana | kopya=0 -> kayip
birebir: 2
yinelenen: 0
ezilen: 1
kayip: 1

İkinci ve dördüncü sürüm aynı nedenle reddedildi: sertifikaları aygıtın güvendiğiyle eşleşmiyor. Kurala göre ise biri ezilen, öteki kayıptır. İkinci sürümden sonra aynı anahtarla daha büyük bir seq ulaştı, üçüncü sürüm; bu yüzden ikinci ezilendir. Dördüncüden sonra böyle bir kopya gelmedi; kurulu sürüm 3, dördüncüden küçüktür. Kurulu sürüm sonra gelen bir olay olmadığı için dördüncüyü ezmiş sayılmaz, bu yüzden dördüncü kayıptır.

Kullanıcı açısından fark şudur. Ezilen sürüm artık gerekmeyen eski bir sürümdür; kayıp sürüm ise hiç gelmeyen yeni bir sürümdür ve kullanıcı onun düzeltmelerini alamaz. Kural bu farkı bir kategoriyle gösterir ama nedenini, yani anahtarın yanlış olduğunu, söylemez.

Anahtar kaybının en ağır hâli, bir sürümden sonra bütün güncellemelerin farklı bir anahtarla imzalanmasıdır. İlk sürüm güvenilen sertifikayladır, sonraki üçü yeni bir anahtarladır.

// Main.kt — anahtar degisince sonraki butun surumler reddedilir
fun main() {
    val v1 = Signal("uygulama.ana", 1)
    val v2 = Signal("uygulama.ana", 2)
    val v3 = Signal("uygulama.ana", 3)
    val v4 = Signal("uygulama.ana", 4)
    val releases = listOf(
        Release(v1, "sertifika-a"),
        Release(v2, "sertifika-b"),
        Release(v3, "sertifika-b"),
        Release(v4, "sertifika-b"),
    )
    val received = installUpdates(Device("sertifika-a"), releases)
    val arrivals = probe(listOf(v1, v2, v3, v4), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
olay-1: anahtar=uygulama.ana | kopya=1 -> birebir
olay-2: anahtar=uygulama.ana | kopya=0 -> kayip
olay-3: anahtar=uygulama.ana | kopya=0 -> kayip
olay-4: anahtar=uygulama.ana | kopya=0 -> kayip
birebir: 1
yinelenen: 0
ezilen: 0
kayip: 3

İlk sürüm ulaştı, üç yeni sürüm ulaşmadı. Hiçbiri ezilen değildir, çünkü aygıtta kurulu olan en yeni sürüm birinci sürümdür ve onları yerinden edecek daha büyük bir seq ulaşmadı. Anahtar bir kez değiştiğinde kayıp tek sürümle sınırlı kalmaz; sonraki her sürüm aynı kapıya takılır. Önceki ölçümde reddedilen sürümün ardından güvenilen sertifikayla yeni bir sürüm gelince ezilen oluşuyordu; burada böyle bir sürüm yoktur.

Aynı kurulum, aygıt yeni anahtara geçtikten sonra tersine döner. Kullanıcı eski uygulamayı kaldırıp yeni anahtarla imzalı sürümü temiz kurar; aygıtın güvendiği sertifika artık sertifika-b’dir.

// Main.kt — temiz kurulumdan sonra aygit yeni sertifikaya guvenir
fun main() {
    val v1 = Signal("uygulama.ana", 1)
    val v2 = Signal("uygulama.ana", 2)
    val v3 = Signal("uygulama.ana", 3)
    val releases = listOf(
        Release(v1, "sertifika-a"),
        Release(v2, "sertifika-b"),
        Release(v3, "sertifika-b"),
    )
    val received = installUpdates(Device("sertifika-b"), releases)
    val arrivals = probe(listOf(v1, v2, v3), received)
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
olay-1: anahtar=uygulama.ana | kopya=0 -> ezilen
olay-2: anahtar=uygulama.ana | kopya=1 -> birebir
olay-3: anahtar=uygulama.ana | kopya=1 -> birebir
birebir: 2
yinelenen: 0
ezilen: 1
kayip: 0

Eski anahtarla imzalı birinci sürüm artık reddedilir; ama yerini alan daha yeni sürümler ulaştığı için ezilendir. Aynı üç sürüm, güvenilen sertifika değişince kayıp yerine ezilen üretti. Bu, kuralın nedeni değil yalnız kopyaların konumunu gördüğünü bir kez daha gösterir. Temiz kurulumun kullanıcıya bedeli ise ölçümde görünmez: eski uygulamanın verisi kaldırmayla gider ve ölçüm bunu saymaz.

GD3. Model, reddedilen sürümü kurulu sürümden ayrı bir olay olarak ele alır. Platformda reddedilen güncelleme bir hata iletisiyle sonuçlanır ve kurulu uygulama değişmez.

Reddin Sessizliği

installUpdates işlevine bir kez daha bakınca modelin en önemli özelliği görünür: reddedilen sürüm için hiçbir şey üretilmez. Listeye bir hata kaydı eklenmez, işaretçi bırakılmaz, alıcının elinde yalnız kurulanlar kalır. Ölçümü yapan probe de bu yüzden reddedilen sürümü “kopya sıfır” olarak görür. Kayıp ile birebir arasındaki fark, listede bir satırın olup olmamasından ibarettir.

Bu tasarım bilinçlidir, çünkü dersin sorusu alıcıya kaç kopya ulaştığıdır, neden ulaşmadığı değil. Ama okuyan için bir sonuç doğurur: bir sürümün kayıp olması, kaybın nerede yaşandığını söylemez. Sertifika uyuşmazlığı da, paketin yolda bozulması da, kurulumun hiç denenmemesi de aynı sıfırı verir. Üç ayrı neden tek bir sayıda toplanır; neden aramak için ölçümün dışına, yayımlayanın kendi kayıtlarına bakmak gerekir.

Sınıflandırma kuralı ise sıfırı iki kola ayırır. Aynı anahtarla daha büyük bir seq ulaştıysa sıfır ezilen, ulaşmadıysa kayıptır. Sertifika reddi bu ayrımı kendisi yapmaz; ayrımı sonradan gelen sürümler yapar. Aynı reddedilmiş sürüm, ardından güvenilen sertifikayla bir sürüm geldiğinde ezilen, gelmediğinde kayıp yazılır. Bu yüzden bir yayımlayan için en güvenli okuma şudur: kayıp sayısı sıfırdan büyükse, o sürümlerden sonra aygıta hiçbir şey ulaşmamış demektir ve bu, kullanıcının eski sürümde takılı kaldığı anlamına gelir.

Sürümler sırayla gelmek zorunda değildir. Model sürümleri gönderim sırasıyla işler, ama gerçek bir aygıt bir sürümü atlayıp sonrakine geçebilir. Bu durumda atlanan sürüm için kopya sıfırdır ve sonraki sürüm ulaştığı için sınıflandırma ezilen olur, kayıp olmaz. Yani sertifika reddinden bağımsız olarak, “sürüm aygıta hiç inmedi” ile “sürüm indi ama bir sonraki onu yerinden etti” ayrımı yalnız sonradan gelen sürüme bağlıdır.

Sürüm numarası ile sertifika ayrı kimliklerdir. Sürüm numarası kurulumun yönünü belirler, sertifika kurulumun izin verilip verilmediğini. İkisi birbirinin yerine geçmez; büyük numaralı sürüm yanlış sertifikayla gelirse reddedilir, doğru sertifikayla gelen küçük numaralı sürüm ise ölçümde yalnız sıra olarak değerlendirilir.

Yayımlayanın Yapacakları

Bu dersin alıcısı aygıttır, ama kaybı önleyecek olan yayımlayandır. Kontrol listesi kısadır.

  • İmzalama anahtarını sürümden bağımsız saklamak. Anahtar, sürümün bir parçası değil, uygulamanın kimliğidir. Kaybolursa yukarıdaki üç kayıplı ölçümün hâli yaşanır. Anahtarın yedeği, onu kullanan ortamdan ayrı bir yerde tutulmalıdır.
  • Yayın öncesi sertifika özetini karşılaştırmak. Yeni paketin sertifika özeti, bir önceki sürümünkiyle eşleşmiyorsa yayın durdurulur. Bu karşılaştırma aygıtın yapacağı karşılaştırmanın aynısıdır, yalnız kullanıcıdan önce yapılır.
  • İki yoldan dağıtımı bilinçli yapmak. Aynı sürüm hem mağazadan hem elle dağıtılıyorsa, ikisinin sertifikası aynı olmalıdır. Aksi hâlde biri kurulur, öteki reddedilir ve hangisinin hangisi olduğu kullanıcıya görünmez.
  • Reddi izlenebilir kılmak. Reddedilen güncelleme sunucuya hiçbir şey bildirmez. Yayımlayanın bunu fark etmesinin yolu, kurulu sürüm dağılımına bakmaktır: yeni sürümü hiç görmeyen bir kitle varsa kayıp o kitlededir.

Yaygın Yanılgılar

Paket adı aynıysa güncelleme kurulur. Ad yetmez; sertifika da eşleşmelidir. Dört sürümlü ölçümde bütün sürümlerin adı aynıydı ve ikisi yine de reddedildi.

Reddedilen güncelleme her zaman kayıptır. Değildir. Sonradan güvenilen sertifikayla daha yeni bir sürüm ulaştıysa ezilendir. Kayıp ile ezilen arasındaki fark reddin nedeninde değil, sonradan ne geldiğindedir.

Anahtar değişikliği yalnız bir sürümü etkiler. Etkilemez. Anahtar değişince sonraki her sürüm reddedildi; üç sürümün üçü kayıp oldu.

Aynı sürümün iki kez gelmesi bir hatadır. Aygıt açısından bir hata olmayabilir; ama kural iki kopya ulaştığını sayar ve yinelenen yazar. Hata olup olmadığı yeniden kurulumun sonucuna bağlıdır, sayıya değil.

Özet

  • Bu dersin teslimi: birebir. Aygıtın güvendiği sertifikayla imzalanan güncelleme karşılaştırmadan geçer ve kurulu uygulamaya bir kopya olarak ulaşır.
  • İmzalama anahtarı sürümden sürüme aynı kalması gereken tek şeydir; sertifika paketle aygıta iner.
  • Üç sürümün üçü aynı sertifikayla birebir ulaşır: birebir 3.
  • Aynı sürüm iki yoldan kurulursa iki kopya sayılır: birebir 1, yinelenen 1.
  • Farklı anahtarla imzalanan sürüm reddedilir: sonradan gelen sürüm varsa ezilen, yoksa kayıp. Dört sürümde birebir 2, ezilen 1, kayıp 1. Anahtar bir sürümden sonra değişirse üç sürüm de kayıp olur: birebir 1, kayıp 3.

Sonraki Adım

Sürüm artık aygıttadır ve çalışır. Çalışırken uygulamanın neler yaptığını bilmek için olay toplamak gerekir. Sonraki derste uygulama analitik olayları ve çökme raporları gönderir; gönderim yanıtı kaybolunca aynı olay tekilleştirmeden yeniden denenir.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat