Schema di firma dell'APK v2

Lo schema di firma dell'APK v2 è uno schema di firma dell'intero file che aumenta la velocità di verifica e rafforza le garanzie di integrità rilevando eventuali modifiche alle parti protette dell'APK.

La firma con lo schema di firma dell'APK v2 inserisce un blocco di firma dell'APK nel file APK immediatamente prima della sezione del directory centrale ZIP. All'interno del blocco di firma dell'APK, le firme v2 e le informazioni sull'identità del firmatario sono memorizzate in un blocco dello schema di firma dell'APK v2.

APK prima e dopo la firma

Figura 1. APK prima e dopo la firma

Lo schema di firma dell'APK v2 è stato introdotto in Android 7.0 (Nougat). Per poter installare un APK su Android 6.0 (Marshmallow) e versioni precedenti, l'APK deve essere firmato utilizzando la firma JAR prima di essere firmato con lo schema v2.

Blocco di firma dell'APK

Per mantenere la compatibilità con il formato APK v1, le firme APK v2 e successive vengono memorizzate in un blocco di firma APK, un nuovo contenitore introdotto per supportare lo schema di firma dell'APK v2. In un file APK, il blocco di firma dell'APK si trova immediatamente prima del Directory centrale ZIP, che si trova alla fine del file.

Il blocco contiene coppie ID-valore racchiuse in modo da semplificarne la localizzazione nell'APK. La firma v2 dell'APK è memorizzata come coppia ID-valore con ID 0x7109871a.

Formato

Il formato del blocco di firma dell'APK è il seguente (tutti i campi numerici sono in little-endian):

  • size of block in byte (escluso questo campo) (uint64)
  • Sequenza di coppie ID-valore con prefisso di lunghezza uint64:
    • ID (uint32)
    • value (lunghezza variabile: lunghezza della coppia - 4 byte)
  • size of block in byte, come il primo campo (uint64)
  • magic "APK Sig Block 42" (16 byte)

L'APK viene analizzato cercando prima l'inizio del Directory centrale ZIP (trovandovi il record ZIP End of Central Directory alla fine del file, quindi leggendo l'offset di inizio del Directory centrale dal record). Il valore magic offre un modo rapido per stabilire che ciò che precede la directory centrale è probabilmente il blocco di firma dell'APK. Il valore size of block indica quindi in modo efficiente l'inizio del blocco nel file.

Le coppie ID-valore con ID sconosciuti devono essere ignorate durante l'interpretazione del blocco.

Blocco dello schema di firma dell'APK v2

L'APK è firmato da uno o più firmatari/identità, ciascuno rappresentato da una chiave di firma. Queste informazioni vengono memorizzate come blocco dello schema di firma dell'APK v2. Per ogni firmatario vengono memorizzate le seguenti informazioni:

  • Tuple (algoritmo di firma, digest, firma). Il digest viene memorizzato per scollegare la verifica della firma dal controllo dell'integrità dei contenuti dell'APK.
  • La catena di certificati X.509 che rappresenta l'identità del firmatario.
  • Attributi aggiuntivi sotto forma di coppie chiave-valore.

Per ogni firmatario, l'APK viene verificato utilizzando una firma supportata dall'elenco fornito. Le firme con algoritmi di firma sconosciuti vengono ignorate. È compito di ogni implementazione scegliere quale firma utilizzare quando vengono rilevate più firme supportate. Ciò consente di introdurre in futuro metodi di firma più efficaci in modo compatibile con le versioni precedenti. L'approccio suggerito è verificare la firma più sicura.

Formato

Il blocco dello schema di firma dell'APK v2 è memorizzato all'interno del blocco di firma dell'APK nell'ID 0x7109871a.

Il formato del blocco dello schema di firma dell'APK v2 è il seguente (tutti i valori numerici sono little-endian, tutti i campi con prefisso di lunghezza utilizzano uint32 per la lunghezza):

  • sequenza con prefisso di lunghezza di signer con prefisso di lunghezza:
    • signed data con prefisso di lunghezza:
      • sequenza con prefisso di lunghezza di digests con prefisso di lunghezza:
      • sequenza con prefisso di lunghezza di X.509 certificates:
        • X.509 con prefisso di lunghezza certificate (formato ASN.1 DER)
      • sequenza con prefisso di lunghezza di additional attributes con prefisso di lunghezza:
        • ID (uint32)
        • value (lunghezza variabile: lunghezza dell'attributo aggiuntivo - 4 byte)
    • sequenza con prefisso di lunghezza di signatures con prefisso di lunghezza:
      • signature algorithm ID (uint32)
      • signature con prefisso di lunghezza su signed data
    • con prefisso di lunghezza public key (SubjectPublicKeyInfo, formato DER ASN.1)

ID algoritmo di firma

  • 0x0101: RSASSA-PSS con digest SHA2-256, MGF1 SHA2-256, 32 byte di salt, trailer: 0xbc
  • 0x0102 - RSASSA-PSS con digest SHA2-512, MGF1 SHA2-512, 64 byte di salt, trailer: 0xbc
  • 0x0103: RSASSA-PKCS1-v1_5 con digest SHA2-256. Questo vale per i sistemi di build che richiedono firme deterministiche.
  • 0x0104: RSASSA-PKCS1-v1_5 con digest SHA2-512. Questo vale per i sistemi di build che richiedono firme deterministiche.
  • 0x0201: ECDSA con digest SHA2-256
  • 0x0202: ECDSA con digest SHA2-512
  • 0x0301: DSA con digest SHA2-256

Tutti gli algoritmi di firma sopra indicati sono supportati dalla piattaforma Android. Gli strumenti di firma possono supportare un sottoinsieme di algoritmi.

Dimensioni delle chiavi e curve EC supportate:

  • RSA: 1024, 2048, 4096, 8192, 16384
  • EC: NIST P-256, P-384, P-521
  • DSA: 1024, 2048, 3072

Contenuti con integrità protetta

Ai fini della protezione dei contenuti dell'APK, un APK è costituito da quattro sezioni:

  1. Contenuti delle voci ZIP (dall'offset 0 fino all'inizio del blocco di firma dell'APK)
  2. Blocco di firma dell'APK
  3. Directory centrale ZIP
  4. Fine della directory centrale ZIP

Sezioni dell'APK dopo la firma

Figura 2. Sezioni dell'APK dopo la firma

Lo schema di firma dell'APK v2 protegge l'integrità delle sezioni 1, 3, 4 e dei blocchi signed data dello schema di firma dell'APK v2 contenuti all'interno della sezione 2.

L'integrità delle sezioni 1, 3 e 4 è protetta da uno o più digest dei relativi contenuti memorizzati in blocchi signed data che, a loro volta, sono protetti da una o più firme.

Il digest delle sezioni 1, 3 e 4 viene calcolato come segue, in modo simile a un albero di Merkle a due livelli. Ogni sezione è suddivisa in blocchi consecutivi di 1 MB (220 byte). L'ultimo chunk in ogni sezione potrebbe essere più breve. Il digest di ogni chunk viene calcolato sulla concatenazione del byte 0xa5, della lunghezza del chunk in byte (uint32 little-endian) e dei contenuti del chunk. Il digest di primo livello viene calcolato sulla concatenazione del byte 0x5a, del numero di chunk (uint32 little-endian) e della concatenazione dei digest dei chunk nell'ordine in cui appaiono nell'APK. Il digest viene calcolato a blocchi per consentire di velocizzare il calcolo parallelizzandolo.

Digest APK

Figura 3. Digest APK

La protezione della sezione 4 (fine della directory centrale ZIP) è complicata dalla sezione contenente l'offset della directory centrale ZIP. L'offset cambia quando le dimensioni del blocco di firma dell'APK cambiano, ad esempio quando viene aggiunto un nuovo blocco di firma. Pertanto, quando si calcola il digest nell'End of Central Directory ZIP, il campo contenente l'offset del ZIP Central Directory deve essere considerato come contenente l'offset del blocco di firma APK.

Protezioni di rollback

Un malintenzionato potrebbe tentare di far verificare un APK firmato con la versione 2 come APK firmato con la versione 1 su piattaforme Android che supportano la verifica degli APK firmati con la versione 2. Per mitigare questo attacco, gli APK firmati con la versione 2 e anche con la versione 1 devono contenere un attributo X-Android-APK-Signed nella sezione principale dei file META-INF/*.SF. Il valore dell'attributo è un insieme di ID schema di firma dell'APK separati da virgole (l'ID di questo schema è 2). Durante la verifica della firma v1, il verificatore APK è tenuto a rifiutare gli APK che non dispongono di una firma per lo schema di firma dell'APK preferito dal verificatore da questo insieme (ad es. lo schema v2). Questa protezione si basa sul fatto che i contenuti dei file META-INF/*.SF sono protetti dalle firme v1. Consulta la sezione sulla verifica dell'APK firmato JAR.

Un utente malintenzionato potrebbe tentare di rimuovere le firme più stringenti dal blocco dello schema di firma dell'APK v2. Per mitigare questo attacco, l'elenco degli ID degli algoritmi di firma con cui è stato firmato l'APK è memorizzato nel blocco signed data protetto da ogni firma.

Verifica

In Android 7.0 e versioni successive, gli APK possono essere verificati in base allo schema di firma dell'APK v2 o successivo o alla firma JAR (schema v1). Le piattaforme precedenti ignorano le firme v2 e verificano solo le firme v1.

Procedura di verifica della firma dell'APK

Figura 4. Procedura di verifica della firma dell'APK (nuovi passaggi in rosso)

Verifica dello schema di firma dell'APK v2

  1. Individua il blocco di firma dell'APK e verifica che:
    1. Due campi di dimensioni del blocco di firma dell'APK contengono lo stesso valore.
    2. Il Directory centrale ZIP è immediatamente seguito dal record Fine directory centrale ZIP.
    3. La fine del directory centrale ZIP non è seguita da altri dati.
  2. Individua il primo blocco dello schema di firma dell'APK v2 all'interno del blocco di firma dell'APK. Se il blocco v2 è presente, vai al passaggio 3. In caso contrario, utilizza la verifica dell'APK utilizzando lo schema v1.
  3. Per ogni signer nel blocco dello schema di firma dell'APK v2:
    1. Scegli il signature algorithm ID più potente supportato da signatures. L'ordine di importanza dipende da ogni implementazione/versione della piattaforma.
    2. Verifica il signature corrispondente da signatures rispetto a signed data utilizzando public key. Ora è possibile analizzare signed data.
    3. Verifica che l'elenco ordinato degli ID degli algoritmi di firma in digests e signatures sia identico. (Questo serve a evitare la rimozione/l'aggiunta della firma).
    4. Calcola il digest dei contenuti dell'APK utilizzando lo stesso algoritmo di digest utilizzato dall'algoritmo di firma.
    5. Verifica che il digest calcolato sia identico al corrispondente digest di digests.
    6. Verifica che SubjectPublicKeyInfo del primo certificate di certificates sia identico a public key.
  4. La verifica è riuscita se è stato trovato almeno un signer e il passaggio 3 è andato a buon fine per ogni signer trovato.

Nota: l'APK non deve essere verificato utilizzando lo schema v1 se si verifica un errore nel passaggio 3 o 4.

Verifica dell'APK firmato JAR (schema v1)

L'APK firmato JAR è un file JAR firmato standard, che deve contenere esattamente le voci elencate in META-INF/MANIFEST.MF e in cui tutte le voci devono essere firmate dallo stesso insieme di firmatari. La sua integrità viene verificata nel seguente modo:

  1. Ogni firmatario è rappresentato da una voce JAR META-INF/<signer>.SF e META-INF/<signer>.(RSA|DSA|EC).
  2. <signer>.(RSA|DSA|EC) è un ContentInfo CMS PKCS #7 con struttura SignedData la cui firma viene verificata sul file <signer>.SF.
  3. Il file <signer>.SF contiene un digest dell'intero file META-INF/MANIFEST.MF e i digest di ogni sezione di META-INF/MANIFEST.MF. Il digest dell'intero file MANIFEST.MF è verificato. In caso di esito negativo, viene verificata la digest di ogni sezione MANIFEST.MF.
  4. META-INF/MANIFEST.MF contiene, per ogni voce JAR protetta dall'integrità, una sezione denominata di conseguenza contenente il digest dei contenuti non compressi della voce. Tutti questi digest sono verificati.
  5. La verifica dell'APK non va a buon fine se l'APK contiene voci JAR non elencate in MANIFEST.MF e non fanno parte della firma JAR.

La catena di protezione è quindi <signer>.(RSA|DSA|EC) -> <signer>.SF -> MANIFEST.MF -> contenuti di ogni voce JAR protetta dall'integrità.