Optimize boot signing to use as little memory as possible

This commit is contained in:
topjohnwu 2018-09-18 23:48:21 -04:00
parent 2c0436216f
commit ac7467fb59
3 changed files with 41 additions and 67 deletions

View File

@ -2,7 +2,6 @@ package com.topjohnwu.magisk.container;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.text.TextUtils;
import java.util.List; import java.util.List;

View File

@ -15,7 +15,6 @@ import java.security.Key;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPrivateKeySpec;
@ -27,8 +26,8 @@ import java.util.Map;
class CryptoUtils { class CryptoUtils {
private static final Map<String, String> ID_TO_ALG; static final Map<String, String> ID_TO_ALG;
private static final Map<String, String> ALG_TO_ID; static final Map<String, String> ALG_TO_ID;
static { static {
ID_TO_ALG = new HashMap<>(); ID_TO_ALG = new HashMap<>();
@ -47,7 +46,7 @@ class CryptoUtils {
ALG_TO_ID.put("SHA512withRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption.getId()); ALG_TO_ID.put("SHA512withRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption.getId());
} }
private static String getSignatureAlgorithm(Key key) throws Exception { static String getSignatureAlgorithm(Key key) throws Exception {
if ("EC".equals(key.getAlgorithm())) { if ("EC".equals(key.getAlgorithm())) {
int curveSize; int curveSize;
KeyFactory factory = KeyFactory.getInstance("EC"); KeyFactory factory = KeyFactory.getInstance("EC");
@ -82,25 +81,6 @@ class CryptoUtils {
return new AlgorithmIdentifier(new ASN1ObjectIdentifier(id)); return new AlgorithmIdentifier(new ASN1ObjectIdentifier(id));
} }
static boolean verify(PublicKey key, byte[] input, byte[] signature,
AlgorithmIdentifier algId) throws Exception {
String algName = ID_TO_ALG.get(algId.getAlgorithm().getId());
if (algName == null) {
throw new IllegalArgumentException("Unsupported algorithm " + algId.getAlgorithm());
}
Signature verifier = Signature.getInstance(algName);
verifier.initVerify(key);
verifier.update(input);
return verifier.verify(signature);
}
static byte[] sign(PrivateKey privateKey, byte[] input) throws Exception {
Signature signer = Signature.getInstance(getSignatureAlgorithm(privateKey));
signer.initSign(privateKey);
signer.update(input);
return signer.sign();
}
static X509Certificate readCertificate(InputStream input) static X509Certificate readCertificate(InputStream input)
throws IOException, GeneralSecurityException { throws IOException, GeneralSecurityException {
try { try {

View File

@ -23,6 +23,7 @@ import java.nio.ByteOrder;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.Security; import java.security.Security;
import java.security.Signature;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
@ -37,20 +38,17 @@ public class SignBoot {
public static boolean doSignature(String target, InputStream imgIn, OutputStream imgOut, public static boolean doSignature(String target, InputStream imgIn, OutputStream imgOut,
InputStream cert, InputStream key) { InputStream cert, InputStream key) {
try { try {
ByteArrayStream bas = new ByteArrayStream(); ByteArrayStream image = new ByteArrayStream();
bas.readFrom(imgIn); image.readFrom(imgIn);
byte[] image = bas.toByteArray(); int signableSize = getSignableImageSize(image.getBuf());
bas.close(); if (signableSize < image.size()) {
int signableSize = getSignableImageSize(image);
if (signableSize < image.length) {
System.err.println("NOTE: truncating input from " + System.err.println("NOTE: truncating input from " +
image.length + " to " + signableSize + " bytes"); image.size() + " to " + signableSize + " bytes");
image = Arrays.copyOf(image, signableSize); } else if (signableSize > image.size()) {
} else if (signableSize > image.length) {
throw new IllegalArgumentException("Invalid image: too short, expected " + throw new IllegalArgumentException("Invalid image: too short, expected " +
signableSize + " bytes"); signableSize + " bytes");
} }
BootSignature bootsig = new BootSignature(target, image.length); BootSignature bootsig = new BootSignature(target, image.size());
if (cert == null) { if (cert == null) {
cert = SignBoot.class.getResourceAsStream("/keys/testkey.x509.pem"); cert = SignBoot.class.getResourceAsStream("/keys/testkey.x509.pem");
} }
@ -60,10 +58,10 @@ public class SignBoot {
key = SignBoot.class.getResourceAsStream("/keys/testkey.pk8"); key = SignBoot.class.getResourceAsStream("/keys/testkey.pk8");
} }
PrivateKey privateKey = CryptoUtils.readPrivateKey(key); PrivateKey privateKey = CryptoUtils.readPrivateKey(key);
bootsig.setSignature(bootsig.sign(image, privateKey), bootsig.setSignature(bootsig.sign(privateKey, image.getBuf(), signableSize),
CryptoUtils.getSignatureAlgorithmIdentifier(privateKey)); CryptoUtils.getSignatureAlgorithmIdentifier(privateKey));
byte[] encoded_bootsig = bootsig.getEncoded(); byte[] encoded_bootsig = bootsig.getEncoded();
imgOut.write(image); image.writeTo(imgOut);
imgOut.write(encoded_bootsig); imgOut.write(encoded_bootsig);
imgOut.flush(); imgOut.flush();
return true; return true;
@ -75,21 +73,19 @@ public class SignBoot {
public static boolean verifySignature(InputStream imgIn, InputStream certIn) { public static boolean verifySignature(InputStream imgIn, InputStream certIn) {
try { try {
ByteArrayStream bas = new ByteArrayStream(); ByteArrayStream image = new ByteArrayStream();
bas.readFrom(imgIn); image.readFrom(imgIn);
byte[] image = bas.toByteArray(); int signableSize = getSignableImageSize(image.getBuf());
bas.close(); if (signableSize >= image.size()) {
int signableSize = getSignableImageSize(image);
if (signableSize >= image.length) {
System.err.println("Invalid image: not signed"); System.err.println("Invalid image: not signed");
return false; return false;
} }
byte[] signature = Arrays.copyOfRange(image, signableSize, image.length); byte[] signature = Arrays.copyOfRange(image.getBuf(), signableSize, image.size());
BootSignature bootsig = new BootSignature(signature); BootSignature bootsig = new BootSignature(signature);
if (certIn != null) { if (certIn != null) {
bootsig.setCertificate(CryptoUtils.readCertificate(certIn)); bootsig.setCertificate(CryptoUtils.readCertificate(certIn));
} }
if (bootsig.verify(Arrays.copyOf(image, signableSize))) { if (bootsig.verify(image.getBuf(), signableSize)) {
System.err.println("Signature is VALID"); System.err.println("Signature is VALID");
return true; return true;
} else { } else {
@ -130,7 +126,7 @@ public class SignBoot {
static class BootSignature extends ASN1Object { static class BootSignature extends ASN1Object {
private ASN1Integer formatVersion; private ASN1Integer formatVersion;
private ASN1Encodable certificate; private ASN1Encodable certificate;
private AlgorithmIdentifier algorithmIdentifier; private AlgorithmIdentifier algId;
private DERPrintableString target; private DERPrintableString target;
private ASN1Integer length; private ASN1Integer length;
private DEROctetString signature; private DEROctetString signature;
@ -167,8 +163,7 @@ public class SignBoot {
X509Certificate c = (X509Certificate) cf.generateCertificate(bis); X509Certificate c = (X509Certificate) cf.generateCertificate(bis);
publicKey = c.getPublicKey(); publicKey = c.getPublicKey();
ASN1Sequence algId = (ASN1Sequence) sequence.getObjectAt(2); ASN1Sequence algId = (ASN1Sequence) sequence.getObjectAt(2);
algorithmIdentifier = new AlgorithmIdentifier( this.algId = new AlgorithmIdentifier((ASN1ObjectIdentifier) algId.getObjectAt(0));
(ASN1ObjectIdentifier) algId.getObjectAt(0));
ASN1Sequence attrs = (ASN1Sequence) sequence.getObjectAt(3); ASN1Sequence attrs = (ASN1Sequence) sequence.getObjectAt(3);
target = (DERPrintableString) attrs.getObjectAt(0); target = (DERPrintableString) attrs.getObjectAt(0);
length = (ASN1Integer) attrs.getObjectAt(1); length = (ASN1Integer) attrs.getObjectAt(1);
@ -187,38 +182,38 @@ public class SignBoot {
} }
public void setSignature(byte[] sig, AlgorithmIdentifier algId) { public void setSignature(byte[] sig, AlgorithmIdentifier algId) {
algorithmIdentifier = algId; this.algId = algId;
signature = new DEROctetString(sig); signature = new DEROctetString(sig);
} }
public void setCertificate(X509Certificate cert) public void setCertificate(X509Certificate cert)
throws Exception, IOException, CertificateEncodingException { throws CertificateEncodingException, IOException {
ASN1InputStream s = new ASN1InputStream(cert.getEncoded()); ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
certificate = s.readObject(); certificate = s.readObject();
publicKey = cert.getPublicKey(); publicKey = cert.getPublicKey();
} }
public byte[] generateSignableImage(byte[] image) throws IOException { public byte[] sign(PrivateKey key, byte[] image, int length) throws Exception {
byte[] attrs = getEncodedAuthenticatedAttributes(); Signature signer = Signature.getInstance(CryptoUtils.getSignatureAlgorithm(key));
byte[] signable = Arrays.copyOf(image, image.length + attrs.length); signer.initSign(key);
for (int i=0; i < attrs.length; i++) { signer.update(image, 0, length);
signable[i+image.length] = attrs[i]; signer.update(getEncodedAuthenticatedAttributes());
} return signer.sign();
return signable;
} }
public byte[] sign(byte[] image, PrivateKey key) throws Exception { public boolean verify(byte[] image, int length) throws Exception {
byte[] signable = generateSignableImage(image); if (this.length.getValue().intValue() != length) {
return CryptoUtils.sign(key, signable);
}
public boolean verify(byte[] image) throws Exception {
if (length.getValue().intValue() != image.length) {
throw new IllegalArgumentException("Invalid image length"); throw new IllegalArgumentException("Invalid image length");
} }
byte[] signable = generateSignableImage(image); String algName = CryptoUtils.ID_TO_ALG.get(algId.getAlgorithm().getId());
return CryptoUtils.verify(publicKey, signable, signature.getOctets(), if (algName == null) {
algorithmIdentifier); throw new IllegalArgumentException("Unsupported algorithm " + algId.getAlgorithm());
}
Signature verifier = Signature.getInstance(algName);
verifier.initVerify(publicKey);
verifier.update(image, 0, length);
verifier.update(getEncodedAuthenticatedAttributes());
return verifier.verify(signature.getOctets());
} }
@Override @Override
@ -226,7 +221,7 @@ public class SignBoot {
ASN1EncodableVector v = new ASN1EncodableVector(); ASN1EncodableVector v = new ASN1EncodableVector();
v.add(formatVersion); v.add(formatVersion);
v.add(certificate); v.add(certificate);
v.add(algorithmIdentifier); v.add(algId);
v.add(getAuthenticatedAttributes()); v.add(getAuthenticatedAttributes());
v.add(signature); v.add(signature);
return new DERSequence(v); return new DERSequence(v);