diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignature.java b/jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignature.java deleted file mode 100644 index 6a0f96c6b..000000000 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignature.java +++ /dev/null @@ -1,250 +0,0 @@ -package jadx.gui.treemodel; - -import java.io.File; -import java.security.cert.Certificate; -import java.util.List; -import java.util.stream.Collectors; - -import javax.swing.Icon; -import javax.swing.ImageIcon; - -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.commons.text.StringEscapeUtils; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.android.apksig.ApkVerifier; - -import jadx.api.ICodeInfo; -import jadx.api.ResourceFile; -import jadx.api.ResourceType; -import jadx.api.impl.SimpleCodeInfo; -import jadx.gui.JadxWrapper; -import jadx.gui.ui.panel.ContentPanel; -import jadx.gui.ui.panel.HtmlPanel; -import jadx.gui.ui.tab.TabbedPane; -import jadx.gui.utils.CertificateManager; -import jadx.gui.utils.NLS; -import jadx.gui.utils.UiUtils; -import jadx.zip.IZipEntry; - -public class ApkSignature extends JNode { - private static final long serialVersionUID = -9121321926113143407L; - - private static final Logger LOG = LoggerFactory.getLogger(ApkSignature.class); - - private static final ImageIcon CERTIFICATE_ICON = UiUtils.openSvgIcon("nodes/styleKeyPack"); - - private final transient File openFile; - private ICodeInfo content; - - @Nullable - public static ApkSignature getApkSignature(JadxWrapper wrapper) { - // Only show the ApkSignature node if an AndroidManifest.xml is present. - // Without a manifest the Google ApkVerifier refuses to work. - File apkFile = null; - for (ResourceFile resFile : wrapper.getResources()) { - if (resFile.getType() == ResourceType.MANIFEST) { - IZipEntry zipEntry = resFile.getZipEntry(); - if (zipEntry != null) { - apkFile = zipEntry.getZipFile(); - break; - } - } - } - if (apkFile == null) { - return null; - } - return new ApkSignature(apkFile); - } - - public ApkSignature(File openFile) { - this.openFile = openFile; - } - - @Override - public JClass getJParent() { - return null; - } - - @Override - public Icon getIcon() { - return CERTIFICATE_ICON; - } - - @Override - public String makeString() { - return "APK signature"; - } - - @Override - public ContentPanel getContentPanel(TabbedPane tabbedPane) { - return new HtmlPanel(tabbedPane, this); - } - - @Override - public ICodeInfo getCodeInfo() { - if (content != null) { - return this.content; - } - ApkVerifier verifier = new ApkVerifier.Builder(openFile).build(); - try { - ApkVerifier.Result result = verifier.verify(); - StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4); - builder.append("
"); - if (result.isVerified()) { - builder.escape(NLS.str("apkSignature.verificationSuccess")); - } else { - builder.escape(NLS.str("apkSignature.verificationFailed")); - } - builder.append("
"); - - final String err = NLS.str("apkSignature.errors"); - final String warn = NLS.str("apkSignature.warnings"); - final String sigSuccKey = "apkSignature.signatureSuccess"; - final String sigFailKey = "apkSignature.signatureFailed"; - - writeIssues(builder, err, result.getErrors()); - - if (!result.getV1SchemeSigners().isEmpty()) { - builder.append(""); - for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) { - builder.append(""); - } - if (!result.getV2SchemeSigners().isEmpty()) { - builder.append(""); - builder.escape(NLS.str("apkSignature.signer")); - builder.append(" "); - builder.escape(signer.getName()); - builder.append(" ("); - builder.escape(signer.getSignatureFileName()); - builder.append(")"); - builder.append("
"); - writeCertificate(builder, signer.getCertificate()); - writeIssues(builder, err, signer.getErrors()); - writeIssues(builder, warn, signer.getWarnings()); - } - builder.append("
"); - for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) { - builder.append(""); - } - if (!result.getV3SchemeSigners().isEmpty()) { - builder.append(""); - builder.escape(NLS.str("apkSignature.signer")); - builder.append(" "); - builder.append(Integer.toString(signer.getIndex() + 1)); - builder.append("
"); - writeCertificate(builder, signer.getCertificate()); - writeIssues(builder, err, signer.getErrors()); - writeIssues(builder, warn, signer.getWarnings()); - } - builder.append("
"); - for (ApkVerifier.Result.V3SchemeSignerInfo signer : result.getV3SchemeSigners()) { - builder.append(""); - } - if (!result.getV31SchemeSigners().isEmpty()) { - builder.append(""); - builder.escape(NLS.str("apkSignature.signer")); - builder.append(" "); - builder.append(Integer.toString(signer.getIndex() + 1)); - builder.append("
"); - writeCertificate(builder, signer.getCertificate()); - writeIssues(builder, err, signer.getErrors()); - writeIssues(builder, warn, signer.getWarnings()); - } - builder.append("
"); - for (ApkVerifier.Result.V3SchemeSignerInfo signer : result.getV31SchemeSigners()) { - builder.append(""); - } - writeIssues(builder, warn, result.getWarnings()); - - this.content = new SimpleCodeInfo(builder.toString()); - } catch (Exception e) { - LOG.error(e.getMessage(), e); - StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4); - builder.append(""); - builder.escape(NLS.str("apkSignature.signer")); - builder.append(" "); - builder.append(Integer.toString(signer.getIndex() + 1)); - builder.append("
"); - writeCertificate(builder, signer.getCertificate()); - writeIssues(builder, err, signer.getErrors()); - writeIssues(builder, warn, signer.getWarnings()); - } - builder.append("
");
- builder.escape(ExceptionUtils.getStackTrace(e));
- builder.append("");
- return new SimpleCodeInfo(builder.toString());
- }
- return this.content;
- }
-
- private void writeCertificate(StringEscapeUtils.Builder builder, Certificate cert) {
- CertificateManager certMgr = new CertificateManager(cert);
- builder.append(""); - } - - private void writeIssues(StringEscapeUtils.Builder builder, String issueType, List"); - builder.escape(certMgr.generateHeader()); - builder.append(""); - builder.escape(certMgr.generatePublicKey()); - builder.append(""); - builder.escape(certMgr.generateSignature()); - builder.append(""); - builder.append(certMgr.generateFingerprint()); - builder.append("
"); - // Unprotected Zip entry issues are very common, handle them separately - List"); - } - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignatureNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignatureNode.java new file mode 100644 index 000000000..42fb4c76a --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignatureNode.java @@ -0,0 +1,320 @@ +package jadx.gui.treemodel; + +import java.io.File; +import java.security.cert.Certificate; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.android.apksig.ApkVerifier; + +import jadx.api.ICodeInfo; +import jadx.api.ResourceFile; +import jadx.api.ResourceType; +import jadx.api.impl.SimpleCodeInfo; +import jadx.gui.JadxWrapper; +import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.ui.panel.HtmlPanel; +import jadx.gui.ui.tab.TabbedPane; +import jadx.gui.utils.CertificateManager; +import jadx.gui.utils.NLS; +import jadx.gui.utils.UiUtils; +import jadx.zip.IZipEntry; + +public class ApkSignatureNode extends JNode { + private static final long serialVersionUID = -9121321926113143407L; + + private static final Logger LOG = LoggerFactory.getLogger(ApkSignatureNode.class); + + private static final ImageIcon CERTIFICATE_ICON = UiUtils.openSvgIcon("nodes/styleKeyPack"); + + private final transient File openFile; + private ICodeInfo content; + private volatile boolean loadingStarted = false; + private static TabbedPane tabbedPane; + + @Nullable + public static ApkSignatureNode getApkSignature(JadxWrapper wrapper) { + // Only show the ApkSignature node if an AndroidManifest.xml is present. + // Without a manifest the Google ApkVerifier refuses to work. + File apkFile = null; + for (ResourceFile resFile : wrapper.getResources()) { + if (resFile.getType() == ResourceType.MANIFEST) { + IZipEntry zipEntry = resFile.getZipEntry(); + if (zipEntry != null) { + apkFile = zipEntry.getZipFile(); + break; + } + } + } + if (apkFile == null) { + return null; + } + return new ApkSignatureNode(apkFile); + } + + public ApkSignatureNode(File openFile) { + this.openFile = openFile; + } + + @Override + public JClass getJParent() { + return null; + } + + @Override + public Icon getIcon() { + return CERTIFICATE_ICON; + } + + @Override + public String makeString() { + return "APK signature"; + } + + @Override + public ContentPanel getContentPanel(TabbedPane tabbedPane) { + ApkSignatureNode.tabbedPane = tabbedPane; + return new HtmlPanel(tabbedPane, this); + } + + @Override + public ICodeInfo getCodeInfo() { + if (content != null) { + return content; + } + + // If loading hasn't started yet, start it. + if (!loadingStarted) { + loadingStarted = true; + SwingUtilities.invokeLater(() -> { + new ApkSignatureWorker(this).execute(); + }); + + return new SimpleCodeInfo(StringEscapeUtils.escapeHtml4(NLS.str("apkSignature.loading"))); + } + + return new SimpleCodeInfo(StringEscapeUtils.escapeHtml4(NLS.str("apkSignature.loading"))); + } + + private void writeCertificate(StringEscapeUtils.Builder builder, Certificate cert) { + CertificateManager certMgr = new CertificateManager(cert); + builder.append("unprotIssues = issueList.stream() - .filter(i -> i.getIssue() == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY) - .collect(Collectors.toList()); - if (!unprotIssues.isEmpty()) { - builder.append(" "); - builder.escape(NLS.str("apkSignature.unprotectedEntry")); - builder.append("
"); - for (ApkVerifier.IssueWithParams issue : unprotIssues) { - builder.escape((String) issue.getParams()[0]); - builder.append(""); - } - List
"); - } - builder.append("remainingIssues = issueList.stream() - .filter(i -> i.getIssue() != ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY) - .collect(Collectors.toList()); - if (!remainingIssues.isEmpty()) { - builder.append(" \n"); - for (ApkVerifier.IssueWithParams issue : remainingIssues) { - builder.escape(issue.toString()); - builder.append("\n"); - } - builder.append("\n"); - } - builder.append("
"); + } + + private static void writeIssues(StringEscapeUtils.Builder builder, String issueType, List"); + builder.escape(certMgr.generateHeader()); + builder.append(""); + builder.escape(certMgr.generatePublicKey()); + builder.append(""); + builder.escape(certMgr.generateSignature()); + builder.append(""); + builder.append(certMgr.generateFingerprint()); + builder.append("
"); + // Unprotected Zip entry issues are very common, handle them separately + List"); + } + } + + private static class ApkSignatureWorker extends SwingWorkerunprotIssues = issueList.stream() + .filter(i -> i.getIssue() == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY) + .collect(Collectors.toList()); + if (!unprotIssues.isEmpty()) { + builder.append(" "); + builder.escape(NLS.str("apkSignature.unprotectedEntry")); + builder.append("
"); + for (ApkVerifier.IssueWithParams issue : unprotIssues) { + builder.escape((String) issue.getParams()[0]); + builder.append(""); + } + List
"); + } + builder.append("remainingIssues = issueList.stream() + .filter(i -> i.getIssue() != ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY) + .collect(Collectors.toList()); + if (!remainingIssues.isEmpty()) { + builder.append(" \n"); + for (ApkVerifier.IssueWithParams issue : remainingIssues) { + builder.escape(issue.toString()); + builder.append("\n"); + } + builder.append("\n"); + } + builder.append("
"); + if (result.isVerified()) { + builder.escape(NLS.str("apkSignature.verificationSuccess")); + } else { + builder.escape(NLS.str("apkSignature.verificationFailed")); + } + builder.append("
"); + + final String err = NLS.str("apkSignature.errors"); + final String warn = NLS.str("apkSignature.warnings"); + final String sigSuccKey = "apkSignature.signatureSuccess"; + final String sigFailKey = "apkSignature.signatureFailed"; + + ApkSignatureNode parentNode = node; + + writeIssues(builder, err, result.getErrors()); + + if (!result.getV1SchemeSigners().isEmpty()) { + builder.append(""); + for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) { + builder.append(""); + } + if (!result.getV2SchemeSigners().isEmpty()) { + builder.append(""); + builder.escape(NLS.str("apkSignature.signer")); + builder.append(" "); + builder.escape(signer.getName()); + builder.append(" ("); + builder.escape(signer.getSignatureFileName()); + builder.append(")"); + builder.append("
"); + parentNode.writeCertificate(builder, signer.getCertificate()); + writeIssues(builder, err, signer.getErrors()); + writeIssues(builder, warn, signer.getWarnings()); + } + builder.append("
"); + for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) { + builder.append(""); + } + if (!result.getV3SchemeSigners().isEmpty()) { + builder.append(""); + builder.escape(NLS.str("apkSignature.signer")); + builder.append(" "); + builder.append(Integer.toString(signer.getIndex() + 1)); + builder.append("
"); + parentNode.writeCertificate(builder, signer.getCertificate()); + writeIssues(builder, err, signer.getErrors()); + writeIssues(builder, warn, signer.getWarnings()); + } + builder.append("
"); + for (ApkVerifier.Result.V3SchemeSignerInfo signer : result.getV3SchemeSigners()) { + builder.append(""); + } + if (!result.getV31SchemeSigners().isEmpty()) { + builder.append(""); + builder.escape(NLS.str("apkSignature.signer")); + builder.append(" "); + builder.append(Integer.toString(signer.getIndex() + 1)); + builder.append("
"); + parentNode.writeCertificate(builder, signer.getCertificate()); + writeIssues(builder, err, signer.getErrors()); + writeIssues(builder, warn, signer.getWarnings()); + } + builder.append("
"); + for (ApkVerifier.Result.V3SchemeSignerInfo signer : result.getV31SchemeSigners()) { + builder.append(""); + } + writeIssues(builder, warn, result.getWarnings()); + + return new SimpleCodeInfo(builder.toString()); + + } catch (Exception e) { + LOG.error("Failed to verify APK signature for {}", node.openFile, e); + StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4); + builder.append(""); + builder.escape(NLS.str("apkSignature.signer")); + builder.append(" "); + builder.append(Integer.toString(signer.getIndex() + 1)); + builder.append("
"); + parentNode.writeCertificate(builder, signer.getCertificate()); + writeIssues(builder, err, signer.getErrors()); + writeIssues(builder, warn, signer.getWarnings()); + } + builder.append("
");
+ builder.escape(ExceptionUtils.getStackTrace(e));
+ builder.append("");
+ return new SimpleCodeInfo(builder.toString());
+ }
+ }
+
+ @Override
+ protected void done() {
+ try {
+ node.content = get();
+ if (tabbedPane != null) {
+ ContentPanel panel = tabbedPane.getTabByNode(node);
+ if (panel instanceof HtmlPanel) {
+ ((HtmlPanel) panel).loadContent(node);
+ }
+ } else {
+ LOG.warn("Could not find TabbedPane to refresh ApkSignatureNode panel.");
+ }
+
+ } catch (InterruptedException | ExecutionException e) {
+ LOG.error("Error during APK signature verification SwingWorker", e);
+ StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4);
+ builder.append("");
+ Throwable cause = (e instanceof ExecutionException) ? e.getCause() : e;
+ builder.escape(ExceptionUtils.getStackTrace(cause));
+ builder.append("");
+ node.content = new SimpleCodeInfo(builder.toString());
+
+ if (tabbedPane != null) {
+ ContentPanel panel = tabbedPane.getTabByNode(node);
+ if (panel instanceof HtmlPanel) {
+ ((HtmlPanel) panel).loadContent(node);
+ }
+ }
+ } finally {
+ node.loadingStarted = false;
+ }
+ }
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
index 9bcf4d83f..041d49ac9 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
@@ -116,7 +116,7 @@ import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.ui.JadxSettingsWindow;
import jadx.gui.settings.ui.plugins.PluginSettings;
import jadx.gui.tree.TreeExpansionService;
-import jadx.gui.treemodel.ApkSignature;
+import jadx.gui.treemodel.ApkSignatureNode;
import jadx.gui.treemodel.JInputFiles;
import jadx.gui.treemodel.JInputScripts;
import jadx.gui.treemodel.JInputs;
@@ -682,7 +682,7 @@ public class MainWindow extends JFrame {
}
private void addTreeCustomNodes() {
- treeRoot.replaceCustomNode(ApkSignature.getApkSignature(wrapper));
+ treeRoot.replaceCustomNode(ApkSignatureNode.getApkSignature(wrapper));
treeRoot.replaceCustomNode(new SummaryNode(this));
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/panel/HtmlPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/panel/HtmlPanel.java
index d9117b7a1..ae3a49eeb 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/panel/HtmlPanel.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/panel/HtmlPanel.java
@@ -23,8 +23,7 @@ public final class HtmlPanel extends ContentPanel {
setLayout(new BorderLayout());
textArea = new JHtmlPane();
loadSettings();
- textArea.setText(jnode.getCodeInfo().getCodeStr());
- textArea.setCaretPosition(0); // otherwise the start view will be the last line
+ loadContent(jnode);
textArea.setEditable(false);
JScrollPane sp = new JScrollPane(textArea);
add(sp);
@@ -38,6 +37,11 @@ public final class HtmlPanel extends ContentPanel {
textArea.setFont(settings.getFont());
}
+ public void loadContent(JNode jnode) {
+ textArea.setText(jnode.getCodeInfo().getCodeStr());
+ textArea.setCaretPosition(0); // otherwise the start view will be the last line
+ }
+
public JEditorPane getHtmlArea() {
return textArea;
}
diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
index 5c07389e3..dae064370 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
@@ -398,6 +398,7 @@ certificate.serialSHA256=SHA-256-Fingerabdruck
certificate.serialPubKeyY=Y
apkSignature.signer=Signierer
+#apkSignature.loading=Loading signature...
apkSignature.verificationSuccess=Signaturprüfung erfolgreich abgeschlossen
apkSignature.verificationFailed=Signaturprüfung fehlgeschlagen
apkSignature.signatureSuccess=Gültige APK-Signatur v%d gefunden
diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
index 90b12db29..6006d0980 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
@@ -398,6 +398,7 @@ certificate.serialSHA256=SHA-256 Fingerprint
certificate.serialPubKeyY=Y
apkSignature.signer=Signer
+apkSignature.loading=Loading signature...
apkSignature.verificationSuccess=Signature verification succeeded
apkSignature.verificationFailed=Signature verification failed
apkSignature.signatureSuccess=Valid APK signature v%d found
diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
index 61867a8ec..38dfdf8ae 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
@@ -398,6 +398,7 @@ certificate.serialSHA256=Huella SHA-256
certificate.serialPubKeyY=Y
#apkSignature.signer=Signer
+#apkSignature.loading=Loading signature...
#apkSignature.verificationSuccess=Signature verification succeeded
#apkSignature.verificationFailed=Signature verification failed
#apkSignature.signatureSuccess=Valid APK signature v%d found
diff --git a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties
index 9e3b3a3e0..e13886915 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties
@@ -398,6 +398,7 @@ certificate.serialSHA256=Sidik Jari SHA-256
certificate.serialPubKeyY=Y
apkSignature.signer=Penandatangan
+#apkSignature.loading=Loading signature...
apkSignature.verificationSuccess=Verifikasi tanda tangan berhasil
apkSignature.verificationFailed=Verifikasi tanda tangan gagal
apkSignature.signatureSuccess=Tanda tangan APK yang valid v%d ditemukan
diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties
index 1b4adbe12..90c2ce004 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties
@@ -398,6 +398,7 @@ certificate.serialSHA256=SHA-256 지문
certificate.serialPubKeyY=Y
apkSignature.signer=서명자
+#apkSignature.loading=Loading signature...
apkSignature.verificationSuccess=서명 검증 성공
apkSignature.verificationFailed=서명 검증 실패
apkSignature.signatureSuccess=유효한 APK 서명 v%d을(를) 찾았습니다.
diff --git a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties
index c3495d189..09a6b4698 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties
@@ -398,6 +398,7 @@ certificate.serialSHA256=Assinatura digital SHA-256
certificate.serialPubKeyY=Y
apkSignature.signer=Assinador
+#apkSignature.loading=Loading signature...
apkSignature.verificationSuccess=Verificação de assinatura bem-sucedida
apkSignature.verificationFailed=Verificação de assinatura falhou
apkSignature.signatureSuccess=Assinatura válida de apk v%d encontrada
diff --git a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties
index 19e15ba18..820e8ecd3 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties
@@ -398,6 +398,7 @@ certificate.serialSHA256=SHA-256 Fingerprint
certificate.serialPubKeyY=Y
apkSignature.signer=Signer
+#apkSignature.loading=Loading signature...
apkSignature.verificationSuccess=Signature verification succeeded
apkSignature.verificationFailed=Signature verification failed
apkSignature.signatureSuccess=Valid APK signature v%d found
diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
index 497fe6ca1..8e361ccb6 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
@@ -398,6 +398,7 @@ certificate.serialSHA256=SHA-256 签名
certificate.serialPubKeyY=Y
apkSignature.signer=签名者
+#apkSignature.loading=Loading signature...
apkSignature.verificationSuccess=签名验证成功
apkSignature.verificationFailed=签名验证失败
apkSignature.signatureSuccess=找到有效的 APK 签名 v%d
diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties
index 2b447eefb..877f2757d 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties
@@ -398,6 +398,7 @@ certificate.serialSHA256=SHA-256 指紋
certificate.serialPubKeyY=Y
apkSignature.signer=簽署者
+#apkSignature.loading=Loading signature...
apkSignature.verificationSuccess=簽名驗證成功
apkSignature.verificationFailed=簽名驗證失敗
apkSignature.signatureSuccess=找到可用的 APK 簽名 v%d