Add boot signing

This commit is contained in:
topjohnwu 2017-10-30 03:45:22 +08:00
parent 2ee0829871
commit 05f41928cd
17 changed files with 460 additions and 68 deletions

View File

@ -54,11 +54,11 @@ repositories {
dependencies { dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':jarsigner') implementation project(':crypto')
implementation 'com.android.support:recyclerview-v7:26.1.0' implementation 'com.android.support:recyclerview-v7:27.0.0'
implementation 'com.android.support:cardview-v7:26.1.0' implementation 'com.android.support:cardview-v7:27.0.0'
implementation 'com.android.support:design:26.1.0' implementation 'com.android.support:design:27.0.0'
implementation 'com.android.support:support-v4:26.1.0' implementation 'com.android.support:support-v4:27.0.0'
implementation 'com.jakewharton:butterknife:8.8.1' implementation 'com.jakewharton:butterknife:8.8.1'
implementation 'com.atlassian.commonmark:commonmark:0.10.0' implementation 'com.atlassian.commonmark:commonmark:0.10.0'
implementation 'org.kamranzafar:jtar:2.3' implementation 'org.kamranzafar:jtar:2.3'

View File

@ -2,7 +2,7 @@ package com.topjohnwu.magisk.asyncs;
import android.app.Activity; import android.app.Activity;
import com.topjohnwu.jarsigner.ByteArrayStream; import com.topjohnwu.crypto.ByteArrayStream;
import com.topjohnwu.magisk.MagiskManager; import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.WebService; import com.topjohnwu.magisk.utils.WebService;

View File

@ -3,7 +3,7 @@ package com.topjohnwu.magisk.asyncs;
import android.os.Environment; import android.os.Environment;
import android.widget.Toast; import android.widget.Toast;
import com.topjohnwu.jarsigner.JarMap; import com.topjohnwu.crypto.JarMap;
import com.topjohnwu.magisk.MagiskManager; import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.database.SuDatabaseHelper; import com.topjohnwu.magisk.database.SuDatabaseHelper;

View File

@ -14,6 +14,7 @@ import android.view.WindowManager;
import com.topjohnwu.magisk.MagiskManager; import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Topic; import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.Utils;
public class Activity extends AppCompatActivity { public class Activity extends AppCompatActivity {
@ -88,14 +89,9 @@ public class Activity extends AppCompatActivity {
@Keep @Keep
public void swapResources(String dexPath) { public void swapResources(String dexPath) {
try { mAssetManager = Utils.getAssets(dexPath);
AssetManager asset = AssetManager.class.newInstance(); if (mAssetManager == null)
AssetManager.class.getMethod("addAssetPath", String.class).invoke(asset, dexPath);
mAssetManager = asset;
} catch (Exception e) {
e.printStackTrace();
return; return;
}
Resources res = super.getResources(); Resources res = super.getResources();
mResources = new Resources(mAssetManager, res.getDisplayMetrics(), res.getConfiguration()); mResources = new Resources(mAssetManager, res.getDisplayMetrics(), res.getConfiguration());
mResources.newTheme().setTo(super.getTheme()); mResources.newTheme().setTo(super.getTheme());

View File

@ -3,8 +3,9 @@ package com.topjohnwu.magisk.services;
import android.app.IntentService; import android.app.IntentService;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.support.v7.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.R;
public class OnBootIntentService extends IntentService { public class OnBootIntentService extends IntentService {
@ -19,7 +20,8 @@ public class OnBootIntentService extends IntentService {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this); NotificationCompat.Builder builder =
new NotificationCompat.Builder(this, MagiskManager.NOTIFICATION_CHANNEL);
builder.setSmallIcon(R.drawable.ic_magisk) builder.setSmallIcon(R.drawable.ic_magisk)
.setContentTitle("onBoot") .setContentTitle("onBoot")
.setContentText("Running onBoot operations..."); .setContentText("Running onBoot operations...");

View File

@ -0,0 +1,46 @@
package com.topjohnwu.magisk.utils;
import android.content.res.AssetManager;
import com.topjohnwu.crypto.SignBoot;
import java.io.FileInputStream;
import java.io.InputStream;
public class BootSigner {
public static void main(String[] args) throws Exception {
if ("-verify".equals(args[0])) {
String certPath = "";
if (args.length >= 4 && "-certificate".equals(args[2])) {
/* args[3] is the path to a public key certificate */
certPath = args[3];
}
/* args[1] is the path to a signed boot image */
boolean signed = SignBoot.verifySignature(args[1],
certPath.isEmpty() ? null : new FileInputStream(certPath));
System.exit(signed ? 0 : 1);
} else {
/* args[0] is the target name, typically /boot
args[1] is the path to a boot image to sign
args[2] is the path where to output the signed boot image
args[3] is the path to a private key
args[4] is the path to the matching public key certificate
*/
InputStream keyIn, sigIn;
if (args.length >= 5) {
keyIn = new FileInputStream(args[3]);
sigIn = new FileInputStream(args[4]);
} else {
/* Use internal test keys */
AssetManager asset = Utils.getAssets(System.getProperty("java.class.path"));
if (asset == null)
System.exit(1);
keyIn = asset.open(ZipUtils.PRIVATE_KEY_NAME);
sigIn = asset.open(ZipUtils.PUBLIC_KEY_NAME);
}
SignBoot.doSignature(args[0], args[1], args[2], keyIn, sigIn);
}
}
}

View File

@ -7,6 +7,7 @@ import android.content.Context;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.database.Cursor; import android.database.Cursor;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
@ -203,4 +204,15 @@ public class Utils {
public static File getDatabasePath(Context context, String dbName) { public static File getDatabasePath(Context context, String dbName) {
return new File(context.getFilesDir().getParent() + "/databases", dbName); return new File(context.getFilesDir().getParent() + "/databases", dbName);
} }
public static AssetManager getAssets(String apk) {
try {
AssetManager asset = AssetManager.class.newInstance();
AssetManager.class.getMethod("addAssetPath", String.class).invoke(asset, apk);
return asset;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
} }

View File

@ -2,8 +2,8 @@ package com.topjohnwu.magisk.utils;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import com.topjohnwu.jarsigner.JarMap; import com.topjohnwu.crypto.JarMap;
import com.topjohnwu.jarsigner.SignAPK; import com.topjohnwu.crypto.SignAPK;
import com.topjohnwu.magisk.MagiskManager; import com.topjohnwu.magisk.MagiskManager;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
@ -16,8 +16,8 @@ import java.util.jar.JarInputStream;
public class ZipUtils { public class ZipUtils {
// File name in assets // File name in assets
private static final String PUBLIC_KEY_NAME = "public.certificate.x509.pem"; static final String PUBLIC_KEY_NAME = "public.certificate.x509.pem";
private static final String PRIVATE_KEY_NAME = "private.key.pk8"; static final String PRIVATE_KEY_NAME = "private.key.pk8";
static { static {
System.loadLibrary("zipadjust"); System.loadLibrary("zipadjust");

View File

@ -8,13 +8,14 @@ targetCompatibility = "1.8"
jar { jar {
manifest { manifest {
attributes 'Main-Class': 'com.topjohnwu.jarsigner.CommandLine' attributes 'Main-Class': 'com.topjohnwu.crypto.ZipSigner'
} }
} }
shadowJar { shadowJar {
classifier = 'fat' baseName = 'zipsigner'
version = null classifier = null
version = 1.0
} }
buildscript { buildscript {

View File

@ -1,4 +1,4 @@
package com.topjohnwu.jarsigner; package com.topjohnwu.crypto;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;

View File

@ -0,0 +1,136 @@
package com.topjohnwu.crypto;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
class CryptoUtils {
private static final Map<String, String> ID_TO_ALG;
private static final Map<String, String> ALG_TO_ID;
static {
ID_TO_ALG = new HashMap<>();
ALG_TO_ID = new HashMap<>();
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA256.getId(), "SHA256withECDSA");
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA384.getId(), "SHA384withECDSA");
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA512.getId(), "SHA512withECDSA");
ID_TO_ALG.put(PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), "SHA1withRSA");
ID_TO_ALG.put(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), "SHA256withRSA");
ID_TO_ALG.put(PKCSObjectIdentifiers.sha512WithRSAEncryption.getId(), "SHA512withRSA");
ALG_TO_ID.put("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256.getId());
ALG_TO_ID.put("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384.getId());
ALG_TO_ID.put("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512.getId());
ALG_TO_ID.put("SHA1withRSA", PKCSObjectIdentifiers.sha1WithRSAEncryption.getId());
ALG_TO_ID.put("SHA256withRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption.getId());
ALG_TO_ID.put("SHA512withRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption.getId());
}
private static String getSignatureAlgorithm(Key key) throws Exception {
if ("EC".equals(key.getAlgorithm())) {
int curveSize;
KeyFactory factory = KeyFactory.getInstance("EC");
if (key instanceof PublicKey) {
ECPublicKeySpec spec = factory.getKeySpec(key, ECPublicKeySpec.class);
curveSize = spec.getParams().getCurve().getField().getFieldSize();
} else if (key instanceof PrivateKey) {
ECPrivateKeySpec spec = factory.getKeySpec(key, ECPrivateKeySpec.class);
curveSize = spec.getParams().getCurve().getField().getFieldSize();
} else {
throw new InvalidKeySpecException();
}
if (curveSize <= 256) {
return "SHA256withECDSA";
} else if (curveSize <= 384) {
return "SHA384withECDSA";
} else {
return "SHA512withECDSA";
}
} else if ("RSA".equals(key.getAlgorithm())) {
return "SHA256withRSA";
} else {
throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
}
}
static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) throws Exception {
String id = ALG_TO_ID.get(getSignatureAlgorithm(key));
if (id == null) {
throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
}
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 readPublicKey(InputStream input)
throws IOException, GeneralSecurityException {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(input);
} finally {
input.close();
}
}
/** Read a PKCS#8 format private key. */
static PrivateKey readPrivateKey(InputStream input)
throws IOException, GeneralSecurityException {
try {
byte[] buffer = new byte[4096];
int size = input.read(buffer);
byte[] bytes = Arrays.copyOf(buffer, size);
/* Check to see if this is in an EncryptedPrivateKeyInfo structure. */
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
/*
* Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
* OID and use that to construct a KeyFactory.
*/
ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
return KeyFactory.getInstance(algOid).generatePrivate(spec);
} finally {
input.close();
}
}
}

View File

@ -1,4 +1,4 @@
package com.topjohnwu.jarsigner; package com.topjohnwu.crypto;
import java.io.Closeable; import java.io.Closeable;
import java.io.File; import java.io.File;

View File

@ -1,10 +1,9 @@
package com.topjohnwu.jarsigner; package com.topjohnwu.crypto;
import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DEROutputStream; import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers; import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.cert.jcajce.JcaCertStore; import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray; import org.bouncycastle.cms.CMSProcessableByteArray;
@ -20,7 +19,6 @@ import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.encoders.Base64; import org.bouncycastle.util.encoders.Base64;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -31,15 +29,12 @@ import java.io.OutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.security.DigestOutputStream; import java.security.DigestOutputStream;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.Provider; import java.security.Provider;
import java.security.Security; import java.security.Security;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -69,8 +64,8 @@ public class SignAPK {
private static final int USE_SHA256 = 2; private static final int USE_SHA256 = 2;
static { static {
SignAPK.sBouncyCastleProvider = new BouncyCastleProvider(); sBouncyCastleProvider = new BouncyCastleProvider();
Security.insertProviderAt(SignAPK.sBouncyCastleProvider, 1); Security.insertProviderAt(sBouncyCastleProvider, 1);
} }
public static void signZip(InputStream publicIn, InputStream privateIn, public static void signZip(InputStream publicIn, InputStream privateIn,
@ -78,14 +73,14 @@ public class SignAPK {
int alignment = 4; int alignment = 4;
BufferedOutputStream outputFile; BufferedOutputStream outputFile;
int hashes = 0; int hashes = 0;
X509Certificate publicKey = readPublicKey(publicIn); X509Certificate publicKey = CryptoUtils.readPublicKey(publicIn);
hashes |= getDigestAlgorithm(publicKey); hashes |= getDigestAlgorithm(publicKey);
// Set the ZIP file timestamp to the starting valid time // Set the ZIP file timestamp to the starting valid time
// of the 0th certificate plus one hour (to match what // of the 0th certificate plus one hour (to match what
// we've historically done). // we've historically done).
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000; long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
PrivateKey privateKey = readPrivateKey(privateIn); PrivateKey privateKey = CryptoUtils.readPrivateKey(privateIn);
outputFile = new BufferedOutputStream(new FileOutputStream(output)); outputFile = new BufferedOutputStream(new FileOutputStream(output));
if (minSign) { if (minSign) {
@ -144,37 +139,7 @@ public class SignAPK {
private static Pattern stripPattern = private static Pattern stripPattern =
Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA|EC)|com/android/otacert))|(" + Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA|EC)|com/android/otacert))|(" +
Pattern.quote(JarFile.MANIFEST_NAME) + ")$"); Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
private static X509Certificate readPublicKey(InputStream input)
throws IOException, GeneralSecurityException {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(input);
} finally {
input.close();
}
}
/** Read a PKCS#8 format private key. */
private static PrivateKey readPrivateKey(InputStream input)
throws IOException, GeneralSecurityException {
try {
byte[] buffer = new byte[4096];
int size = input.read(buffer);
byte[] bytes = Arrays.copyOf(buffer, size);
/* Check to see if this is in an EncryptedPrivateKeyInfo structure. */
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
/*
* Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
* OID and use that to construct a KeyFactory.
*/
ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
return KeyFactory.getInstance(algOid).generatePrivate(spec);
} finally {
input.close();
}
}
/** /**
* Add the hash(es) of every file to the manifest, creating it if * Add the hash(es) of every file to the manifest, creating it if
* necessary. * necessary.

View File

@ -0,0 +1,234 @@
package com.topjohnwu.crypto;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
public class SignBoot {
static {
Security.addProvider(new BouncyCastleProvider());
}
public static void doSignature(String target, String imagePath, String outPath,
InputStream keyIn, InputStream certIn) throws Exception {
doSignature(target, new FileInputStream(imagePath),
new FileOutputStream(outPath), keyIn, certIn);
}
public static void doSignature(String target, InputStream imgIn, OutputStream imgOut,
InputStream keyIn, InputStream certIn) throws Exception {
ByteArrayStream bas = new ByteArrayStream();
bas.readFrom(imgIn);
byte[] image = bas.toByteArray();
bas.close();
imgIn.close();
int signableSize = getSignableImageSize(image);
if (signableSize < image.length) {
System.err.println("NOTE: truncating input from " +
image.length + " to " + signableSize + " bytes");
image = Arrays.copyOf(image, signableSize);
} else if (signableSize > image.length) {
throw new IllegalArgumentException("Invalid image: too short, expected " +
signableSize + " bytes");
}
BootSignature bootsig = new BootSignature(target, image.length);
X509Certificate cert = CryptoUtils.readPublicKey(certIn);
bootsig.setCertificate(cert);
PrivateKey key = CryptoUtils.readPrivateKey(keyIn);
bootsig.setSignature(bootsig.sign(image, key),
CryptoUtils.getSignatureAlgorithmIdentifier(key));
byte[] encoded_bootsig = bootsig.getEncoded();
imgOut.write(image);
imgOut.write(encoded_bootsig);
imgOut.flush();
imgOut.close();
}
public static boolean verifySignature(String imagePath, InputStream certPath) throws Exception {
ByteArrayStream bas = new ByteArrayStream();
bas.readFrom(new FileInputStream(imagePath));
byte[] image = bas.toByteArray();
bas.close();
int signableSize = getSignableImageSize(image);
if (signableSize >= image.length) {
System.err.println("Invalid image: not signed");
return false;
}
byte[] signature = Arrays.copyOfRange(image, signableSize, image.length);
BootSignature bootsig = new BootSignature(signature);
if (certPath != null) {
bootsig.setCertificate(CryptoUtils.readPublicKey(certPath));
}
try {
if (bootsig.verify(Arrays.copyOf(image, signableSize))) {
System.err.println("Signature is VALID");
return true;
} else {
System.err.println("Signature is INVALID");
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public static int getSignableImageSize(byte[] data) throws Exception {
if (!Arrays.equals(Arrays.copyOfRange(data, 0, 8),
"ANDROID!".getBytes("US-ASCII"))) {
throw new IllegalArgumentException("Invalid image header: missing magic");
}
ByteBuffer image = ByteBuffer.wrap(data);
image.order(ByteOrder.LITTLE_ENDIAN);
image.getLong(); // magic
int kernelSize = image.getInt();
image.getInt(); // kernel_addr
int ramdskSize = image.getInt();
image.getInt(); // ramdisk_addr
int secondSize = image.getInt();
image.getLong(); // second_addr + tags_addr
int pageSize = image.getInt();
int length = pageSize // include the page aligned image header
+ ((kernelSize + pageSize - 1) / pageSize) * pageSize
+ ((ramdskSize + pageSize - 1) / pageSize) * pageSize
+ ((secondSize + pageSize - 1) / pageSize) * pageSize;
length = ((length + pageSize - 1) / pageSize) * pageSize;
if (length <= 0) {
throw new IllegalArgumentException("Invalid image header: invalid length");
}
return length;
}
static class BootSignature extends ASN1Object {
private ASN1Integer formatVersion;
private ASN1Encodable certificate;
private AlgorithmIdentifier algorithmIdentifier;
private DERPrintableString target;
private ASN1Integer length;
private DEROctetString signature;
private PublicKey publicKey;
private static final int FORMAT_VERSION = 1;
/**
* Initializes the object for signing an image file
* @param target Target name, included in the signed data
* @param length Length of the image, included in the signed data
*/
public BootSignature(String target, int length) {
this.formatVersion = new ASN1Integer(FORMAT_VERSION);
this.target = new DERPrintableString(target);
this.length = new ASN1Integer(length);
}
/**
* Initializes the object for verifying a signed image file
* @param signature Signature footer
*/
public BootSignature(byte[] signature)
throws Exception {
ASN1InputStream stream = new ASN1InputStream(signature);
ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
formatVersion = (ASN1Integer) sequence.getObjectAt(0);
if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
throw new IllegalArgumentException("Unsupported format version");
}
certificate = sequence.getObjectAt(1);
byte[] encoded = ((ASN1Object) certificate).getEncoded();
ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate c = (X509Certificate) cf.generateCertificate(bis);
publicKey = c.getPublicKey();
ASN1Sequence algId = (ASN1Sequence) sequence.getObjectAt(2);
algorithmIdentifier = new AlgorithmIdentifier(
(ASN1ObjectIdentifier) algId.getObjectAt(0));
ASN1Sequence attrs = (ASN1Sequence) sequence.getObjectAt(3);
target = (DERPrintableString) attrs.getObjectAt(0);
length = (ASN1Integer) attrs.getObjectAt(1);
this.signature = (DEROctetString) sequence.getObjectAt(4);
}
public ASN1Object getAuthenticatedAttributes() {
ASN1EncodableVector attrs = new ASN1EncodableVector();
attrs.add(target);
attrs.add(length);
return new DERSequence(attrs);
}
public byte[] getEncodedAuthenticatedAttributes() throws IOException {
return getAuthenticatedAttributes().getEncoded();
}
public void setSignature(byte[] sig, AlgorithmIdentifier algId) {
algorithmIdentifier = algId;
signature = new DEROctetString(sig);
}
public void setCertificate(X509Certificate cert)
throws Exception, IOException, CertificateEncodingException {
ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
certificate = s.readObject();
publicKey = cert.getPublicKey();
}
public byte[] generateSignableImage(byte[] image) throws IOException {
byte[] attrs = getEncodedAuthenticatedAttributes();
byte[] signable = Arrays.copyOf(image, image.length + attrs.length);
for (int i=0; i < attrs.length; i++) {
signable[i+image.length] = attrs[i];
}
return signable;
}
public byte[] sign(byte[] image, PrivateKey key) throws Exception {
byte[] signable = generateSignableImage(image);
return CryptoUtils.sign(key, signable);
}
public boolean verify(byte[] image) throws Exception {
if (length.getValue().intValue() != image.length) {
throw new IllegalArgumentException("Invalid image length");
}
byte[] signable = generateSignableImage(image);
return CryptoUtils.verify(publicKey, signable, signature.getOctets(),
algorithmIdentifier);
}
@Override
public ASN1Primitive toASN1Primitive() {
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(formatVersion);
v.add(certificate);
v.add(algorithmIdentifier);
v.add(getAuthenticatedAttributes());
v.add(signature);
return new DERSequence(v);
}
}
}

View File

@ -1,4 +1,4 @@
package com.topjohnwu.jarsigner; package com.topjohnwu.crypto;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
@ -7,7 +7,7 @@ import java.io.FileInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.security.Security; import java.security.Security;
public class CommandLine { public class ZipSigner {
public static void main(String[] args) { public static void main(String[] args) {
boolean minSign = false; boolean minSign = false;
int argStart = 0; int argStart = 0;

View File

@ -1 +1 @@
include ':app', ':snet', ':jarsigner' include ':app', ':snet', ':crypto'