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 utilizzando lo schema di firma dell'APK v2 consente di inserire un blocco di firma dell'APK nel file APK immediatamente prima della sezione della directory centrale di 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 firma 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 della directory centrale ZIP, che si trova alla fine del file.

Il blocco contiene coppie ID-valore aggregate in modo da semplificare l'individuazione del blocco 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 fornisce 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 disaccoppiare 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 come 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. Spetta a ciascuna implementazione scegliere quale firma utilizzare nel caso in cui vengano rilevate più firme supportate. Ciò consente l'introduzione di metodi di firma più efficaci in futuro 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 con ID 0x7109871a.

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

  • Sequenza con prefisso lunghezza di signer con prefisso lunghezza:
    • signed data con prefisso di lunghezza:
      • Sequenza con prefisso lunghezza di digests con prefisso 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 superiore a 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 protetti dall'integrità

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

  1. Contenuti delle voci ZIP (da 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 degli 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 una o più sintesi dei contenuti archiviati in blocchi signed data, che sono, a loro volta, 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 da 1 MB (220 byte). L'ultimo chunk in ogni sezione potrebbe essere più breve. Il digest di ogni blocco viene calcolato sulla concatenazione del byte 0xa5, sulla lunghezza del blocco in byte (little-endian uint32) e sui contenuti del blocco. 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 in modo suddiviso per consentire di accelerare 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 la dimensione del blocco di firma dell'APK cambia, ad esempio quando viene aggiunta una nuova firma. Pertanto, quando si calcola il digest sull'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 l'APK è stato firmato viene archiviato nel blocco signed data protetto da ciascuna 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 meno recenti ignorano le firme v2 e verificano solo quelle 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 relativi alle 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 è presente il blocco v2, 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 di ID 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 il valore SubjectPublicKeyInfo dei primi 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à è verificata come segue:

  1. Ogni firmatario è rappresentato da una voce JAR META-INF/<signer>.SF e META-INF/<signer>.(RSA|DSA|EC).
  2. <signer>.(RSA|DSA|EC) è una ContentInfo CMS PKCS #7 con struttura SignedData la cui firma è verificata nel 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. Se non riesce, viene verificato il 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à.