-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(gui): APK signature check v1/v2 using the apksig library from Go…
…ogle (#431) * feat: APK signature check v1/v2 using the apksig library from Google * fix: proposed changes implemented
- Loading branch information
Showing
9 changed files
with
301 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,7 @@ allprojects { | |
mavenLocal() | ||
mavenCentral() | ||
jcenter() | ||
google() | ||
} | ||
|
||
jacoco { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
184 changes: 184 additions & 0 deletions
184
jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignature.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
package jadx.gui.treemodel; | ||
|
||
import com.android.apksig.ApkVerifier; | ||
import jadx.api.ResourceType; | ||
import jadx.gui.JadxWrapper; | ||
import jadx.gui.utils.CertificateManager; | ||
import jadx.gui.utils.NLS; | ||
import jadx.gui.utils.Utils; | ||
import org.apache.commons.lang3.exception.ExceptionUtils; | ||
import org.apache.commons.text.StringEscapeUtils; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import javax.swing.*; | ||
import java.io.File; | ||
import java.security.cert.Certificate; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
public class ApkSignature extends JNode { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(ApkSignature.class); | ||
private static final ImageIcon CERTIFICATE_ICON = Utils.openIcon("certificate_obj"); | ||
|
||
private final transient File openFile; | ||
private String content = null; | ||
|
||
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. | ||
if (!wrapper.getResources().stream().anyMatch(r -> "AndroidManifest.xml".equals(r.getName()))) { | ||
return null; | ||
} | ||
File openFile = wrapper.getOpenFile(); | ||
return new ApkSignature(openFile); | ||
} | ||
|
||
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 String getContent() { | ||
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("<h1>APK signature verification result:</h1>"); | ||
|
||
builder.append("<p><b>"); | ||
if (result.isVerified()) { | ||
builder.escape(NLS.str("apkSignature.verificationSuccess")); | ||
} else { | ||
builder.escape(NLS.str("apkSignature.verificationFailed")); | ||
} | ||
builder.append("</b></p>"); | ||
|
||
final String err = NLS.str("apkSignature.errors"); | ||
final String warn = NLS.str("apkSignature.warnings"); | ||
final String sigSucc = NLS.str("apkSignature.signatureSuccess"); | ||
final String sigFail = NLS.str("apkSignature.signatureFailed"); | ||
|
||
writeIssues(builder, err, result.getErrors()); | ||
writeIssues(builder, warn, result.getWarnings()); | ||
|
||
if (result.getV1SchemeSigners().size() > 0) { | ||
builder.append("<h2>"); | ||
builder.escape(String.format(result.isVerifiedUsingV1Scheme() ? sigSucc : sigFail, 1)); | ||
builder.append("</h2>\n"); | ||
|
||
builder.append("<blockquote>"); | ||
for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) { | ||
builder.append("<h3>"); | ||
builder.escape(NLS.str("apkSignature.signer")); | ||
builder.append(" "); | ||
builder.escape(signer.getName()); | ||
builder.append(" ("); | ||
builder.escape(signer.getSignatureFileName()); | ||
builder.append(")"); | ||
builder.append("</h3>"); | ||
writeCertificate(builder, signer.getCertificate()); | ||
writeIssues(builder, err, signer.getErrors()); | ||
writeIssues(builder, warn, signer.getWarnings()); | ||
} | ||
builder.append("</blockquote>"); | ||
} | ||
if (result.getV2SchemeSigners().size() > 0) { | ||
builder.append("<h2>"); | ||
builder.escape(String.format(result.isVerifiedUsingV2Scheme() ? sigSucc : sigFail, 2)); | ||
builder.append("</h2>\n"); | ||
|
||
builder.append("<blockquote>"); | ||
for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) { | ||
builder.append("<h3>"); | ||
builder.escape(NLS.str("apkSignature.signer")); | ||
builder.append(" "); | ||
builder.append(Integer.toString(signer.getIndex() + 1)); | ||
builder.append("</h3>"); | ||
writeCertificate(builder, signer.getCertificate()); | ||
writeIssues(builder, err, signer.getErrors()); | ||
writeIssues(builder, warn, signer.getWarnings()); | ||
} | ||
builder.append("</blockquote>"); | ||
} | ||
this.content = builder.toString(); | ||
} catch (Exception e) { | ||
log.error(e.getMessage(), e); | ||
StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4); | ||
builder.append("<h1>"); | ||
builder.escape(NLS.str("apkSignature.exception")); | ||
builder.append("</h1><pre>"); | ||
builder.escape(ExceptionUtils.getStackTrace(e)); | ||
builder.append("</pre>"); | ||
return builder.toString(); | ||
} | ||
return this.content; | ||
} | ||
|
||
private void writeCertificate(StringEscapeUtils.Builder builder, Certificate cert) { | ||
CertificateManager certMgr = new CertificateManager(cert); | ||
builder.append("<blockquote><pre>"); | ||
builder.escape(certMgr.generateHeader()); | ||
builder.append("</pre><pre>"); | ||
builder.escape(certMgr.generatePublicKey()); | ||
builder.append("</pre><pre>"); | ||
builder.escape(certMgr.generateSignature()); | ||
builder.append("</pre><pre>"); | ||
builder.append(certMgr.generateFingerprint()); | ||
builder.append("</pre></blockquote>"); | ||
} | ||
|
||
private void writeIssues(StringEscapeUtils.Builder builder, String issueType, List<ApkVerifier.IssueWithParams> issueList) { | ||
if (issueList.size() > 0) { | ||
builder.append("<h3>"); | ||
builder.escape(issueType); | ||
builder.append("</h3>"); | ||
builder.append("<blockquote>"); | ||
// Unprotected Zip entry issues are very common, handle them separately | ||
List<ApkVerifier.IssueWithParams> unprotIssues = issueList.stream().filter(i -> | ||
i.getIssue() == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY).collect(Collectors.toList()); | ||
if (unprotIssues.size() > 0) { | ||
builder.append("<h4>"); | ||
builder.escape(NLS.str("apkSignature.unprotectedEntry")); | ||
builder.append("</h4><blockquote>"); | ||
for (ApkVerifier.IssueWithParams issue : unprotIssues) { | ||
builder.escape((String) issue.getParams()[0]); | ||
builder.append("<br>"); | ||
} | ||
builder.append("</blockquote>"); | ||
} | ||
List<ApkVerifier.IssueWithParams> remainingIssues = issueList.stream().filter(i -> | ||
i.getIssue() != ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY).collect(Collectors.toList()); | ||
if (remainingIssues.size() > 0) { | ||
builder.append("<pre>\n"); | ||
for (ApkVerifier.IssueWithParams issue : remainingIssues) { | ||
builder.escape(issue.toString()); | ||
builder.append("\n"); | ||
} | ||
builder.append("</pre>\n"); | ||
} | ||
builder.append("</blockquote>"); | ||
} | ||
|
||
} | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package jadx.gui.ui; | ||
|
||
import jadx.gui.settings.JadxSettings; | ||
import jadx.gui.treemodel.JNode; | ||
import jadx.gui.ui.codearea.CodeArea; | ||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; | ||
|
||
import javax.swing.*; | ||
import javax.swing.event.DocumentEvent; | ||
import javax.swing.event.DocumentListener; | ||
import javax.swing.plaf.PanelUI; | ||
import java.awt.*; | ||
import java.awt.event.KeyEvent; | ||
import java.awt.event.KeyListener; | ||
|
||
public final class HtmlPanel extends ContentPanel { | ||
private static final long serialVersionUID = -6251262855835426245L; | ||
|
||
private final JHtmlPane textArea; | ||
|
||
public HtmlPanel(TabbedPane panel, JNode jnode) { | ||
super(panel, jnode); | ||
setLayout(new BorderLayout()); | ||
textArea = new JHtmlPane(); | ||
loadSettings(); | ||
textArea.setText(jnode.getContent()); | ||
textArea.setCaretPosition(0); // otherwise the start view will be the last line | ||
textArea.setEditable(false); | ||
JScrollPane sp = new JScrollPane(textArea); | ||
add(sp); | ||
} | ||
|
||
@Override | ||
public void loadSettings() { | ||
JadxSettings settings = getTabbedPane().getMainWindow().getSettings(); | ||
textArea.setFont(settings.getFont()); | ||
} | ||
|
||
private static class JHtmlPane extends JEditorPane { | ||
|
||
boolean antiAliasingEnabled; | ||
|
||
public JHtmlPane() { | ||
setContentType("text/html"); | ||
} | ||
|
||
public void paint(Graphics g) { | ||
Graphics2D g2d = (Graphics2D) g.create(); | ||
try { | ||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); | ||
super.paint(g2d); | ||
} finally { | ||
g2d.dispose(); | ||
} | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.