diff --git a/iped-app/resources/config/conf/CategoriesConfig.json b/iped-app/resources/config/conf/CategoriesConfig.json
index bdbd5eb63c..c4a8ca502e 100644
--- a/iped-app/resources/config/conf/CategoriesConfig.json
+++ b/iped-app/resources/config/conf/CategoriesConfig.json
@@ -89,7 +89,11 @@
{"name": "Others Chats", "mimes":["application/x-ufed-html-chats", "application/x-ufed-chats-txt", "application/x-ufed-chat", "application/x-ufed-chat-preview"]}
]},
{"name": "USN Journal", "mimes": ["application/x-usnjournal-$J", "application/x-usnjournal-report-html", "application/x-usnjournal-report-csv", "application/x-usnjournal-registry"]},
- {"name": "Programs and Libraries", "mimes": ["application/java-archive", "application/x-dosexec", "application/x-msdownload", "application/x-bat", "application/vnd.ms-cab-compressed", "application/x-font-ttf", "application/pkcs7-signature", "application/vnd.ms-htmlhelp", "application/java-vm", "application/vnd.ms-pki.seccat", "application/x-ms-installer", "application/x-ufed-html-apps", "application/x-ufed-installedapplication"]},
+ {"name": "Programs and Libraries",
+ "mimes": ["application/java-archive", "application/x-dosexec", "application/x-msdownload", "application/x-bat", "application/vnd.ms-cab-compressed", "application/x-font-ttf", "application/pkcs7-signature", "application/vnd.ms-htmlhelp", "application/java-vm", "application/vnd.ms-pki.seccat", "application/x-ms-installer", "application/x-ufed-html-apps", "application/x-ufed-installedapplication"],
+ "categories":[{"name": "Android Applications", "mimes": ["application/vnd.android.package-archive"]}]
+ },
+
{"name": "Unallocated", "mimes": ["application/x-unallocated"]},
{"name": "File Slacks", "mimes": ["application/x-fileslack"]},
{"name": "Plain Texts", "mimes": ["text"], "categories":[
diff --git a/iped-app/resources/config/conf/MakePreviewConfig.txt b/iped-app/resources/config/conf/MakePreviewConfig.txt
index 271edf63b4..ac285a1553 100644
--- a/iped-app/resources/config/conf/MakePreviewConfig.txt
+++ b/iped-app/resources/config/conf/MakePreviewConfig.txt
@@ -7,7 +7,7 @@
supportedMimes = application/x-msaccess; application/x-lnk; application/x-firefox-savedsession
supportedMimes = application/x-sqlite3; application/sqlite-skype; application/x-win10-timeline; application/x-gdrive-cloud-graph; application/x-gdrive-snapshot
supportedMimes = application/x-whatsapp-db; application/x-whatsapp-db-f; application/x-whatsapp-chatstorage; application/x-whatsapp-chatstorage-f; application/x-shareaza-searches-dat; application/x-msie-cache
-supportedMimes = application/x-prefetch; text/x-vcard; application/x-bittorrent-resume-dat; application/x-bittorrent; application/x-emule-preferences-dat
+supportedMimes = application/x-prefetch; text/x-vcard; application/x-bittorrent-resume-dat; application/x-bittorrent; application/x-emule-preferences-dat; application/vnd.android.package-archive
# List of mimetypes which parsers insert links to other case items into preview
supportedMimesWithLinks = application/x-emule; application/x-emule-part-met; application/x-ares-galaxy; application/x-shareaza-library-dat
\ No newline at end of file
diff --git a/iped-app/resources/config/conf/ParserConfig.xml b/iped-app/resources/config/conf/ParserConfig.xml
index e613913f76..28a97f5cb2 100644
--- a/iped-app/resources/config/conf/ParserConfig.xml
+++ b/iped-app/resources/config/conf/ParserConfig.xml
@@ -361,6 +361,7 @@
+
diff --git a/iped-app/resources/config/profiles/triage/conf/ParserConfig.xml b/iped-app/resources/config/profiles/triage/conf/ParserConfig.xml
index 1c80e087ea..c532fb0842 100644
--- a/iped-app/resources/config/profiles/triage/conf/ParserConfig.xml
+++ b/iped-app/resources/config/profiles/triage/conf/ParserConfig.xml
@@ -348,6 +348,7 @@
+
diff --git a/iped-app/resources/localization/iped-categories.properties b/iped-app/resources/localization/iped-categories.properties
index f2c6123d11..8e94dac9a1 100644
--- a/iped-app/resources/localization/iped-categories.properties
+++ b/iped-app/resources/localization/iped-categories.properties
@@ -56,6 +56,7 @@ Registry\ User\ Communication=Registry\ User\ Communication
Windows\ Recycle=Windows\ Recycle
USN\ Journal=USN\ Journal
Programs\ and\ Libraries=Programs\ and\ Libraries
+Android\ Applications=Android\ Applications
Unallocated=Unallocated
File\ Slacks=File\ Slacks
Multimedia=Multimedia
diff --git a/iped-app/resources/localization/iped-categories_de_DE.properties b/iped-app/resources/localization/iped-categories_de_DE.properties
index cc006869f2..35f48aa332 100644
--- a/iped-app/resources/localization/iped-categories_de_DE.properties
+++ b/iped-app/resources/localization/iped-categories_de_DE.properties
@@ -56,6 +56,7 @@ Registry\ User\ Communication=Registry\ Benutzerkommunikation
Windows\ Recycle=Windows\ Papierkorb
USN\ Journal=USN\ Journal
Programs\ and\ Libraries=Programme\ und\ Bibliotheken
+Android\ Applications=Android\ Applications[TBT]
Unallocated=nicht\ zugeordnet
File\ Slacks=Datei-Slack
Multimedia=Multimedia
diff --git a/iped-app/resources/localization/iped-categories_es_AR.properties b/iped-app/resources/localization/iped-categories_es_AR.properties
index e375df0e33..38741b754a 100644
--- a/iped-app/resources/localization/iped-categories_es_AR.properties
+++ b/iped-app/resources/localization/iped-categories_es_AR.properties
@@ -56,6 +56,7 @@ Registry\ User\ Communication=Registro\ Comunicaciones\ Usuario
Windows\ Recycle=Windows\ Papalera de Reciclaje
USN\ Journal=USN\ Diario
Programs\ and\ Libraries=Programas\ y\ Bibliotecas
+Android\ Applications=Android\ Applications[TBT]
Unallocated=Sin Asignar
File\ Slacks=Archivos\ Slacks
Multimedia=Multimedia
diff --git a/iped-app/resources/localization/iped-categories_it_IT.properties b/iped-app/resources/localization/iped-categories_it_IT.properties
index 77466831c0..4e26f01ea1 100644
--- a/iped-app/resources/localization/iped-categories_it_IT.properties
+++ b/iped-app/resources/localization/iped-categories_it_IT.properties
@@ -56,6 +56,7 @@ Registry\ User\ Communication=Registro\ Comunicazioni\ Utente
Windows\ Recycle=Cestino\ di\ Windows
USN\ Journal=USN\ Journal
Programs\ and\ Libraries=Software\ e\ Librerie
+Android\ Applications=Android\ Applications[TBT]
Unallocated=Spazio\ non\ allocato
File\ Slacks=File\ Slack
Multimedia=Multimedia
diff --git a/iped-app/resources/localization/iped-categories_pt_BR.properties b/iped-app/resources/localization/iped-categories_pt_BR.properties
index 9ea30539bb..959d25913b 100644
--- a/iped-app/resources/localization/iped-categories_pt_BR.properties
+++ b/iped-app/resources/localization/iped-categories_pt_BR.properties
@@ -56,6 +56,7 @@ Registry\ User\ Communication=Registro\ -\ Comunicação\ de\ Usuário
Windows\ Recycle=Lixeira\ do\ Windows
USN\ Journal=Journal\ USN
Programs\ and\ Libraries=Programas\ e\ Bibliotecas
+Android\ Applications=Aplicativos\ Android
Unallocated=Não\ Alocado
File\ Slacks=File\ Slacks
Multimedia=Multimídia
diff --git a/iped-app/resources/localization/iped-parsers-messages.properties b/iped-app/resources/localization/iped-parsers-messages.properties
index 388bc9b039..5f67d0b566 100644
--- a/iped-app/resources/localization/iped-parsers-messages.properties
+++ b/iped-app/resources/localization/iped-parsers-messages.properties
@@ -318,4 +318,19 @@ TelegramReport.joinedByRequest=User joined by Request
TelegramReport.ChannelMigratedFromGroup=This channel migrated from a group
TelegramReport.RecoveredGroup=Recovered deleted group
P2P.FoundInPedoHashDB=* Red lines mean the hashes were found in child porn alert hash databases.
-Win10Mail.NotFound=Not Found
\ No newline at end of file
+Win10Mail.NotFound=Not Found
+APKParser.Permissions=Permissions Required
+APKParser.Manifest=Manifest XML
+APKParser.Package=Package
+APKParser.Version=Version
+APKParser.SDKVersion=SDK Version
+APKParser.Features=Features
+APKParser.Signers=Signers
+APKParser.SignersV2=Signers(V2)
+APKParser.Path=Path
+APKParser.Certificate=Certificate
+APKParser.Algorithm=Algorithm
+APKParser.MD5=MD5
+APKParser.OID=OID
+APKParser.StartDate=Start Date
+APKParser.EndDate=End Date
diff --git a/iped-app/resources/localization/iped-parsers-messages_de_DE.properties b/iped-app/resources/localization/iped-parsers-messages_de_DE.properties
index c75b7e1e40..3db261a758 100644
--- a/iped-app/resources/localization/iped-parsers-messages_de_DE.properties
+++ b/iped-app/resources/localization/iped-parsers-messages_de_DE.properties
@@ -319,3 +319,18 @@ TelegramReport.ChannelMigratedFromGroup=Dieser Kanal ist aus einer Gruppe hervor
TelegramReport.RecoveredGroup=wiederhergestellte gelöschte Gruppe
P2P.FoundInPedoHashDB=* Rote Zeile bedeutet, dass der Hash in der KiPo Hash-Datenbank gefunden wurde.
Win10Mail.NotFound=Nicht gefunden
+APKParser.Permissions=Permissions Required(TBT)
+APKParser.Manifest=Manifest XML(TBT)
+APKParser.Package=Package(TBT)
+APKParser.Version=Version(TBT)
+APKParser.SDKVersion=SDK Version(TBT)
+APKParser.Features=Features(TBT)
+APKParser.Signers=Signers(TBT)
+APKParser.SignersV2=Signers(V2)(TBT)
+APKParser.Path=Path(TBT)
+APKParser.Certificate=Certificate(TBT)
+APKParser.Algorithm=Algorithm(TBT)
+APKParser.MD5=MD5(TBT)
+APKParser.OID=OID(TBT)
+APKParser.StartDate=Start Date(TBT)
+APKParser.EndDate=End Date(TBT)
\ No newline at end of file
diff --git a/iped-app/resources/localization/iped-parsers-messages_es_AR.properties b/iped-app/resources/localization/iped-parsers-messages_es_AR.properties
index c0596fe8cb..7525d9843e 100644
--- a/iped-app/resources/localization/iped-parsers-messages_es_AR.properties
+++ b/iped-app/resources/localization/iped-parsers-messages_es_AR.properties
@@ -319,3 +319,18 @@ TelegramReport.ChannelMigratedFromGroup=Este canal ha migrado desde un grupo
TelegramReport.RecoveredGroup=Grupo borrado recuperado
P2P.FoundInPedoHashDB=* Las líneas rojas significan que los hashtags se encontraron en bases de datos de hashtags de alertas de pornografía infantil.
Win10Mail.NotFound=No encontrado
+APKParser.Permissions=Permissions Required(TBT)
+APKParser.Manifest=Manifest XML(TBT)
+APKParser.Package=Package(TBT)
+APKParser.Version=Version(TBT)
+APKParser.SDKVersion=SDK Version(TBT)
+APKParser.Features=Features(TBT)
+APKParser.Signers=Signers(TBT)
+APKParser.SignersV2=Signers(V2)(TBT)
+APKParser.Path=Path(TBT)
+APKParser.Certificate=Certificate(TBT)
+APKParser.Algorithm=Algorithm(TBT)
+APKParser.MD5=MD5(TBT)
+APKParser.OID=OID(TBT)
+APKParser.StartDate=Start Date(TBT)
+APKParser.EndDate=End Date(TBT)
\ No newline at end of file
diff --git a/iped-app/resources/localization/iped-parsers-messages_it_IT.properties b/iped-app/resources/localization/iped-parsers-messages_it_IT.properties
index db8a50e075..65b14bcddc 100644
--- a/iped-app/resources/localization/iped-parsers-messages_it_IT.properties
+++ b/iped-app/resources/localization/iped-parsers-messages_it_IT.properties
@@ -319,3 +319,18 @@ TelegramReport.ChannelMigratedFromGroup=Questo canale è migrato da un gruppo
TelegramReport.RecoveredGroup=Gruppo cancellato recuperato
P2P.FoundInPedoHashDB=* Le linee rosse indicano che gli hash sono stati trovati nel child porn alert hash databases.
Win10Mail.NotFound=Non trovato
+APKParser.Permissions=Permissions Required(TBT)
+APKParser.Manifest=Manifest XML(TBT)
+APKParser.Package=Package(TBT)
+APKParser.Version=Version(TBT)
+APKParser.SDKVersion=SDK Version(TBT)
+APKParser.Features=Features(TBT)
+APKParser.Signers=Signers(TBT)
+APKParser.SignersV2=Signers(V2)(TBT)
+APKParser.Path=Path(TBT)
+APKParser.Certificate=Certificate(TBT)
+APKParser.Algorithm=Algorithm(TBT)
+APKParser.MD5=MD5(TBT)
+APKParser.OID=OID(TBT)
+APKParser.StartDate=Start Date(TBT)
+APKParser.EndDate=End Date(TBT)
diff --git a/iped-app/resources/localization/iped-parsers-messages_pt_BR.properties b/iped-app/resources/localization/iped-parsers-messages_pt_BR.properties
index 7826525f39..68a0cb587e 100644
--- a/iped-app/resources/localization/iped-parsers-messages_pt_BR.properties
+++ b/iped-app/resources/localization/iped-parsers-messages_pt_BR.properties
@@ -319,3 +319,18 @@ TelegramReport.ChannelMigratedFromGroup=Este canal migrou de um grupo
TelegramReport.RecoveredGroup=Grupo apagado recuperado
P2P.FoundInPedoHashDB=* Linhas em vermelho indicam que os hashes foram encontrados em bases de hashes de alerta de pornografia infantil
Win10Mail.NotFound=Não Encontrado
+APKParser.Permissions=Permissões requeridas
+APKParser.Manifest=Manifest XML
+APKParser.Package=Pacote
+APKParser.Version=Versão
+APKParser.SDKVersion=Versão do SDK
+APKParser.Features=Características
+APKParser.Signers=Assinaturas
+APKParser.SignersV2=Assinaturas(V2)
+APKParser.Path=Caminho
+APKParser.Certificate=Certificado
+APKParser.Algorithm=Algorítmo
+APKParser.MD5=MD5
+APKParser.OID=OID
+APKParser.StartDate=Início
+APKParser.EndDate=Válido até
diff --git a/iped-app/src/main/resources/iped/app/ui/cat/Android Applications.png b/iped-app/src/main/resources/iped/app/ui/cat/Android Applications.png
new file mode 100644
index 0000000000..79011b0445
Binary files /dev/null and b/iped-app/src/main/resources/iped/app/ui/cat/Android Applications.png differ
diff --git a/iped-parsers/iped-parsers-impl/pom.xml b/iped-parsers/iped-parsers-impl/pom.xml
index 278c7d4f45..a7ede2343f 100644
--- a/iped-parsers/iped-parsers-impl/pom.xml
+++ b/iped-parsers/iped-parsers-impl/pom.xml
@@ -232,6 +232,11 @@
RagingMoose
c92dba11561cb1423dd81e3bb9dea8ae92a392d3
+
+ net.dongliu
+ apk-parser
+ 2.6.10
+
diff --git a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/apk/APKParser.java b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/apk/APKParser.java
new file mode 100644
index 0000000000..fd94bf6f6a
--- /dev/null
+++ b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/apk/APKParser.java
@@ -0,0 +1,304 @@
+package iped.parsers.apk;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TimeZone;
+
+import javax.imageio.ImageIO;
+
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.extractor.EmbeddedDocumentExtractor;
+import org.apache.tika.extractor.ParsingEmbeddedDocumentExtractor;
+import org.apache.tika.io.TemporaryResources;
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.HttpHeaders;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.AbstractParser;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.sax.XHTMLContentHandler;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import iped.parsers.util.Messages;
+import iped.parsers.whatsapp.Util;
+import iped.utils.IOUtil;
+import iped.utils.ImageUtil;
+import net.dongliu.apk.parser.bean.ApkMeta;
+import net.dongliu.apk.parser.bean.ApkSigner;
+import net.dongliu.apk.parser.bean.ApkV2Signer;
+import net.dongliu.apk.parser.bean.CertificateMeta;
+import net.dongliu.apk.parser.bean.Icon;
+import net.dongliu.apk.parser.bean.IconFace;
+import net.dongliu.apk.parser.bean.UseFeature;
+
+public class APKParser extends AbstractParser {
+ private static final long serialVersionUID = 8308661247390527209L;
+ private static final MediaType apkMimeType = MediaType.application("vnd.android.package-archive");
+ public static final Set SUPPORTED_TYPES = Collections.singleton(apkMimeType);
+
+ @Override
+ public Set getSupportedTypes(ParseContext context) {
+ return SUPPORTED_TYPES;
+ }
+
+ @Override
+ public void parse(InputStream stream, ContentHandler handler, Metadata metadata, ParseContext context)
+ throws IOException, SAXException, TikaException {
+
+ TemporaryResources tmp = new TemporaryResources();
+ CustomApkFile apkFile = null;
+ XHTMLContentHandler xhtml = null;
+ File tmpFile = null;
+ EmbeddedDocumentExtractor extractor = context.get(EmbeddedDocumentExtractor.class,
+ new ParsingEmbeddedDocumentExtractor(context));
+
+ try {
+ TikaInputStream tis = TikaInputStream.get(stream, tmp);
+ tmpFile = tis.getFile();
+
+ apkFile = new CustomApkFile(tmpFile);
+ ApkMeta apkMeta = apkFile.getApkMeta();
+
+ metadata.set(HttpHeaders.CONTENT_TYPE, apkMimeType.toString());
+ metadata.remove(TikaCoreProperties.RESOURCE_NAME_KEY);
+
+ xhtml = new XHTMLContentHandler(handler, metadata);
+ xhtml.startDocument();
+
+ xhtml.startElement("head");
+ xhtml.startElement("meta charset='UTF-8'");
+ xhtml.endElement("meta");
+ xhtml.endElement("head");
+
+ xhtml.startElement("style");
+ xhtml.characters(
+ ".tab {border-collapse: collapse; font-family: Arial, sans-serif; margin-right: 32px; margin-bottom: 32px; } "
+ + "img { width: 64px; } "
+ + ".prop { border: solid; border-width: thin; padding: 3px; text-align: left; background-color:#EEEEEE; vertical-align: middle; white-space: nowrap; } "
+ + ".ico { border: solid; border-width: thin; padding: 2px; text-align: center; background-color:#BBBBBB; vertical-align: middle; } "
+ + ".val { border: solid; border-width: thin; padding: 3px; text-align: left; vertical-align: middle; } ");
+ xhtml.endElement("style");
+ xhtml.newline();
+ xhtml.startElement("table", "class", "tab");
+
+ String name = apkMeta.getName();
+ if (name == null || name.isBlank()) {
+ name = apkMeta.getLabel();
+ }
+
+ Icon icon = null;
+ List iconFaces = apkFile.getAllIcons();
+ for (IconFace f : iconFaces) {
+ if (f instanceof Icon && f.isFile() && f.getData() != null) {
+ if (icon == null || icon.getData().length < f.getData().length) {
+ icon = (Icon) f;
+ }
+ }
+ }
+
+ add(xhtml, icon, name);
+ add(xhtml, Messages.getString("APKParser.Package"), apkMeta.getPackageName(), false);
+ add(xhtml, Messages.getString("APKParser.Version"), apkMeta.getVersionName(), false);
+ add(xhtml, Messages.getString("APKParser.SDKVersion"), apkMeta.getCompileSdkVersion(), false);
+
+ StringBuilder sb = new StringBuilder();
+ Set seenCertificates = new HashSet();
+ List signers = null;
+ try {
+ signers = apkFile.getApkSingers();
+ } catch (Exception e) {
+ }
+ if (signers != null && !signers.isEmpty()) {
+ for (ApkSigner s : signers) {
+ sb.append(Messages.getString("APKParser.Path") + ": ").append(s.getPath()).append("\n");
+
+ for (CertificateMeta m : s.getCertificateMetas()) {
+ if (seenCertificates.add(m.toString())) {
+ sb.append(formatCertificate(m));
+ parseEmbeddedCertificate(m, extractor, xhtml);
+ }
+ }
+ }
+ add(xhtml, Messages.getString("APKParser.Signers"), sb.toString(), true);
+ }
+
+ List signers2 = null;
+ try {
+ signers2 = apkFile.getApkV2Singers();
+ } catch (Exception e) {
+ }
+ if (signers2 != null && !signers2.isEmpty()) {
+ List certificates = new ArrayList();
+ for (ApkV2Signer s : signers2) {
+ for (CertificateMeta m : s.getCertificateMetas()) {
+ if (seenCertificates.add(m.toString())) {
+ certificates.add(m);
+ parseEmbeddedCertificate(m, extractor, xhtml);
+ }
+ }
+ }
+ if (!certificates.isEmpty()) {
+ sb.delete(0, sb.length());
+ for (CertificateMeta m : certificates) {
+ sb.append(formatCertificate(m));
+ }
+ add(xhtml, Messages.getString("APKParser.SignersV2"), sb.toString(), true);
+ }
+ }
+
+ sb.delete(0, sb.length());
+ List features = apkMeta.getUsesFeatures();
+ Collections.sort(features, new Comparator() {
+ public int compare(UseFeature o1, UseFeature o2) {
+ return o1.getName().compareToIgnoreCase(o2.getName());
+ }
+ });
+ for (UseFeature feature : features) {
+ sb.append(feature.getName()).append("\n");
+ }
+ add(xhtml, Messages.getString("APKParser.Features"), sb.toString(), true);
+
+ sb.delete(0, sb.length());
+ List permissions = apkMeta.getUsesPermissions();
+ Collections.sort(permissions);
+ for (String permission : permissions) {
+ sb.append(permission).append("\n");
+ }
+ add(xhtml, Messages.getString("APKParser.Permissions"), sb.toString(), true);
+
+ String manifestXml = apkFile.getManifestXml();
+ add(xhtml, Messages.getString("APKParser.Manifest"), manifestXml, true);
+ } finally {
+ IOUtil.closeQuietly(apkFile);
+ tmp.close();
+
+ if (xhtml != null) {
+ xhtml.endElement("table");
+ xhtml.endDocument();
+ }
+ }
+ }
+
+ private void parseEmbeddedCertificate(CertificateMeta m, EmbeddedDocumentExtractor extractor, ContentHandler xhtml)
+ throws SAXException, IOException {
+ CertificateFactory cf;
+ try {
+ cf = CertificateFactory.getInstance("X.509");
+ X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(m.getData()));
+ Metadata certMetadata = new Metadata();
+ certMetadata.add(TikaCoreProperties.RESOURCE_NAME_KEY, cert.getSubjectX500Principal().getName());
+ extractor.parseEmbedded(new ByteArrayInputStream(m.getData()), xhtml, certMetadata, true);
+ } catch (CertificateException e) {
+ // LOGGER.warn("Invalid certificate data for APK:"+apkFile.get);
+ }
+ }
+
+ private String iconToBase64(Icon icon) throws Exception {
+ BufferedImage img = ImageIO.read(new ByteArrayInputStream(icon.getData()));
+ int size = 128;
+ img = ImageUtil.resizeImage(img, size, size);
+ ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
+ ImageIO.write(img, "png", out);
+ return Util.encodeBase64(out.toByteArray());
+ }
+
+ private void add(XHTMLContentHandler xhtml, Icon icon, String val) throws SAXException {
+ xhtml.startElement("tr");
+
+ xhtml.startElement("td", "class", "ico");
+ String imgBase64 = null;
+ if (icon != null) {
+ try {
+ imgBase64 = iconToBase64(icon);
+ } catch (Exception e) {
+ }
+ }
+ if (imgBase64 != null) {
+ xhtml.startElement("img", "src", "data:image/png;base64," + imgBase64);
+ xhtml.endElement("img");
+ }
+ xhtml.endElement("td");
+
+ xhtml.startElement("td", "class", "val");
+ if (val != null) {
+ val = val.trim();
+ }
+ if (val == null || val.isBlank()) {
+ val = "-";
+ }
+ xhtml.startElement("b");
+ xhtml.characters(val);
+ xhtml.endElement("b");
+ xhtml.endElement("td");
+
+ xhtml.endElement("tr");
+ xhtml.newline();
+ }
+
+ private void add(XHTMLContentHandler xhtml, String prop, String val, boolean isPre) throws SAXException {
+ xhtml.startElement("tr");
+ xhtml.startElement("td", "class", "prop");
+ xhtml.characters(prop);
+ xhtml.endElement("td");
+ xhtml.startElement("td", "class", "val");
+ if (val != null) {
+ val = val.trim();
+ }
+ if (val == null || val.isBlank()) {
+ val = "-";
+ }
+ if (isPre) {
+ xhtml.startElement("pre");
+ xhtml.characters(val);
+ xhtml.endElement("pre");
+ } else {
+ xhtml.characters(val);
+ }
+ xhtml.endElement("td");
+ xhtml.endElement("tr");
+ xhtml.newline();
+ }
+
+ private String formatCertificate(CertificateMeta m) {
+ String[] titles = new String[5];
+ titles[0] = Messages.getString("APKParser.Algorithm");
+ titles[1] = Messages.getString("APKParser.MD5");
+ titles[2] = Messages.getString("APKParser.OID");
+ titles[3] = Messages.getString("APKParser.StartDate");
+ titles[4] = Messages.getString("APKParser.EndDate");
+ int w = 0;
+ for (String t : titles) {
+ w = Math.max(w, t.length());
+ }
+ for (int i = 0; i < titles.length; i++) {
+ titles[i] = String.format(" %-" + w + "s : ", titles[i]);
+ }
+
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss'Z'");
+ df.setTimeZone(TimeZone.getTimeZone("UTC"));
+ StringBuilder sb = new StringBuilder();
+ sb.append(" ").append(Messages.getString("APKParser.Certificate")).append("\n");
+ sb.append(titles[0]).append(m.getSignAlgorithm()).append("\n");
+ sb.append(titles[1]).append(m.getCertMd5().toUpperCase()).append("\n");
+ sb.append(titles[2]).append(m.getSignAlgorithmOID()).append("\n");
+ sb.append(titles[3]).append(df.format(m.getStartDate())).append("\n");
+ sb.append(titles[4]).append(df.format(m.getEndDate())).append("\n");
+ return sb.toString();
+ }
+}
diff --git a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/apk/CustomApkFile.java b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/apk/CustomApkFile.java
new file mode 100644
index 0000000000..1c25589442
--- /dev/null
+++ b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/apk/CustomApkFile.java
@@ -0,0 +1,272 @@
+package iped.parsers.apk;
+
+import java.io.ByteArrayInputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import iped.utils.IOUtil;
+import net.dongliu.apk.parser.AbstractApkFile;
+import net.dongliu.apk.parser.bean.ApkSignStatus;
+import net.dongliu.apk.parser.bean.ApkV2Signer;
+import net.dongliu.apk.parser.bean.CertificateMeta;
+import net.dongliu.apk.parser.parser.CertificateMetas;
+import net.dongliu.apk.parser.struct.signingv2.ApkSigningBlock;
+import net.dongliu.apk.parser.struct.signingv2.Digest;
+import net.dongliu.apk.parser.struct.signingv2.Signature;
+import net.dongliu.apk.parser.struct.signingv2.SignerBlock;
+import net.dongliu.apk.parser.struct.zip.EOCD;
+import net.dongliu.apk.parser.utils.Buffers;
+import net.dongliu.apk.parser.utils.Inputs;
+import net.dongliu.apk.parser.utils.Unsigned;
+
+/**
+ * This class replaces the library net.dongliu.apk.parser.ApkFile, that uses
+ * MappedByteBuffer which may hold a reference to the file being parsed, until
+ * it is garbage collected, preventing the temporary file to be deleted.
+ */
+public class CustomApkFile extends AbstractApkFile implements Closeable {
+
+ private final ZipFile zf;
+ private final File apkFile;
+ private List apkV2Signers;
+
+ public CustomApkFile(File apkFile) throws IOException {
+ this.apkFile = apkFile;
+ this.zf = new ZipFile(apkFile);
+ }
+
+ @Override
+ protected List getAllCertificateData() throws IOException {
+ Enumeration extends ZipEntry> enu = zf.entries();
+ List list = new ArrayList<>();
+ while (enu.hasMoreElements()) {
+ ZipEntry ne = enu.nextElement();
+ if (ne.isDirectory()) {
+ continue;
+ }
+ String name = ne.getName().toUpperCase();
+ if (name.endsWith(".RSA") || name.endsWith(".DSA")) {
+ list.add(new CertificateFile(name, Inputs.readAllAndClose(zf.getInputStream(ne))));
+ }
+ }
+ return list;
+ }
+
+ @Override
+ public byte[] getFileData(String path) throws IOException {
+ ZipEntry entry = zf.getEntry(path);
+ if (entry == null) {
+ return null;
+ }
+
+ InputStream inputStream = zf.getInputStream(entry);
+ return Inputs.readAllAndClose(inputStream);
+ }
+
+ @Override
+ protected ByteBuffer fileData() throws IOException {
+ return null;
+ }
+
+ @Override
+ @Deprecated
+ public ApkSignStatus verifyApk() throws IOException {
+ return null;
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ IOUtil.closeQuietly(zf);
+ }
+
+ /**
+ * Create ApkSignBlockParser for this apk file.
+ *
+ * @return null if do not have sign block
+ */
+ protected ByteBuffer findApkSignBlock() throws IOException {
+ long len = apkFile.length();
+ if (len < 22) {
+ throw new RuntimeException("Not zip file");
+ }
+ int maxEOCDSize = 1 << 18;
+ byte[] buf = new byte[(int) Math.min(len, maxEOCDSize)];
+ try (RandomAccessFile raf = new RandomAccessFile(apkFile, "r")) {
+ raf.seek(len - buf.length);
+ raf.readFully(buf);
+ ByteBuffer buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN);
+ long cdStart = -1;
+ for (int i = buf.length - 22; i >= 0; i--) {
+ int v = buffer.getInt(i);
+ if (v == EOCD.SIGNATURE) {
+ Buffers.position(buffer, i + 16);
+ cdStart = Buffers.readUInt(buffer);
+ break;
+ }
+ }
+ if (cdStart == -1) {
+ return null;
+ }
+ int magicStrLen = 16;
+ buf = new byte[magicStrLen];
+ raf.seek(cdStart - magicStrLen);
+ raf.readFully(buf);
+ buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN);
+ String magic = Buffers.readAsciiString(buffer, magicStrLen);
+ if (!magic.equals(ApkSigningBlock.MAGIC)) {
+ return null;
+ }
+ buf = new byte[8];
+ raf.seek(cdStart - 24);
+ raf.readFully(buf);
+ buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN);
+ int blockSize = Unsigned.ensureUInt(buffer.getLong());
+ raf.seek(cdStart - blockSize - 8);
+ raf.readFully(buf);
+ buffer = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN);
+ int blockSize2 = Unsigned.ensureUInt(buffer.getLong());
+ if (blockSize != blockSize2) {
+ return null;
+ }
+ buf = new byte[blockSize - magicStrLen];
+ raf.readFully(buf);
+ return ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN);
+ }
+ }
+
+ public List getApkV2Singers() throws IOException, CertificateException {
+ if (apkV2Signers == null) {
+ parseApkSigningBlock();
+ }
+ return this.apkV2Signers;
+ }
+
+ private void parseApkSigningBlock() throws IOException, CertificateException {
+ List list = new ArrayList<>();
+ ByteBuffer apkSignBlockBuf = findApkSignBlock();
+ if (apkSignBlockBuf != null) {
+ CustomApkSignBlockParser parser = new CustomApkSignBlockParser(apkSignBlockBuf);
+ ApkSigningBlock apkSigningBlock = parser.parse();
+ for (SignerBlock signerBlock : apkSigningBlock.getSignerBlocks()) {
+ List certificates = signerBlock.getCertificates();
+ List certificateMetas = CertificateMetas.from(certificates);
+ ApkV2Signer apkV2Signer = new ApkV2Signer(certificateMetas);
+ list.add(apkV2Signer);
+ }
+ }
+ this.apkV2Signers = list;
+ }
+}
+
+/**
+ * This class replace ApkSignBlockParser to read more V2 signers.
+ */
+class CustomApkSignBlockParser {
+ private ByteBuffer data;
+
+ public CustomApkSignBlockParser(ByteBuffer data) {
+ this.data = data.order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ public ApkSigningBlock parse() throws CertificateException {
+ // sign block found, read pairs
+ List signerBlocks = new ArrayList<>();
+ while (data.remaining() >= 8) {
+ int id = data.getInt();
+ int size = Unsigned.ensureUInt(data.getInt());
+ if (id == ApkSigningBlock.SIGNING_V2_ID) {
+ ByteBuffer signingV2Buffer = Buffers.sliceAndSkip(data, size);
+ // now only care about apk signing v2 entry
+ while (signingV2Buffer.hasRemaining()) {
+ SignerBlock signerBlock = readSigningV2(signingV2Buffer);
+ signerBlocks.add(signerBlock);
+ }
+ } else {
+ if (data.position() + size >= data.limit()) {
+ break;
+ }
+ Buffers.position(data, data.position() + size);
+ }
+ }
+ return new ApkSigningBlock(signerBlocks);
+ }
+
+ private SignerBlock readSigningV2(ByteBuffer buffer) throws CertificateException {
+ buffer = readLenPrefixData(buffer);
+
+ ByteBuffer signedData = readLenPrefixData(buffer);
+ ByteBuffer digestsData = readLenPrefixData(signedData);
+ List digests = readDigests(digestsData);
+ ByteBuffer certificateData = readLenPrefixData(signedData);
+ List certificates = readCertificates(certificateData);
+ ByteBuffer attributesData = readLenPrefixData(signedData);
+ readAttributes(attributesData);
+
+ ByteBuffer signaturesData = readLenPrefixData(buffer);
+ List signatures = readSignatures(signaturesData);
+
+ readLenPrefixData(buffer);
+ return new SignerBlock(digests, certificates, signatures);
+ }
+
+ private List readDigests(ByteBuffer buffer) {
+ List list = new ArrayList<>();
+ while (buffer.hasRemaining()) {
+ ByteBuffer digestData = readLenPrefixData(buffer);
+ int algorithmID = digestData.getInt();
+ byte[] digest = Buffers.readBytes(digestData);
+ list.add(new Digest(algorithmID, digest));
+ }
+ return list;
+ }
+
+ private List readCertificates(ByteBuffer buffer) throws CertificateException {
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ List certificates = new ArrayList<>();
+ while (buffer.hasRemaining()) {
+ ByteBuffer certificateData = readLenPrefixData(buffer);
+ Certificate certificate = certificateFactory
+ .generateCertificate(new ByteArrayInputStream(Buffers.readBytes(certificateData)));
+ certificates.add((X509Certificate) certificate);
+ }
+ return certificates;
+ }
+
+ private void readAttributes(ByteBuffer buffer) {
+ while (buffer.hasRemaining()) {
+ readLenPrefixData(buffer);
+ }
+ }
+
+ private List readSignatures(ByteBuffer buffer) {
+ List signatures = new ArrayList<>();
+ while (buffer.hasRemaining()) {
+ ByteBuffer signatureData = readLenPrefixData(buffer);
+ int algorithmID = signatureData.getInt();
+ int signatureDataLen = Unsigned.ensureUInt(signatureData.getInt());
+ byte[] signature = Buffers.readBytes(signatureData, signatureDataLen);
+ signatures.add(new Signature(algorithmID, signature));
+ }
+ return signatures;
+ }
+
+ private ByteBuffer readLenPrefixData(ByteBuffer buffer) {
+ int len = Unsigned.ensureUInt(buffer.getInt());
+ return Buffers.sliceAndSkip(buffer, len);
+ }
+}