mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-04-26 03:20:47 +00:00
306 lines
12 KiB
Java
306 lines
12 KiB
Java
package com.topjohnwu.signing;
|
|
|
|
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 java.io.ByteArrayInputStream;
|
|
import java.io.FilterInputStream;
|
|
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.Signature;
|
|
import java.security.cert.CertificateEncodingException;
|
|
import java.security.cert.CertificateFactory;
|
|
import java.security.cert.X509Certificate;
|
|
import java.util.Arrays;
|
|
|
|
public class SignBoot {
|
|
|
|
private static final int BOOT_IMAGE_HEADER_V1_RECOVERY_DTBO_SIZE_OFFSET = 1632;
|
|
private static final int BOOT_IMAGE_HEADER_V2_DTB_SIZE_OFFSET = 1648;
|
|
|
|
private static class PushBackRWStream extends FilterInputStream {
|
|
private OutputStream out;
|
|
private int pos = 0;
|
|
private byte[] backBuf;
|
|
|
|
PushBackRWStream(InputStream in, OutputStream o) {
|
|
super(in);
|
|
out = o;
|
|
}
|
|
|
|
@Override
|
|
public int read() throws IOException {
|
|
int b;
|
|
if (backBuf != null && backBuf.length > pos) {
|
|
b = backBuf[pos++];
|
|
} else {
|
|
b = super.read();
|
|
out.write(b);
|
|
}
|
|
return b;
|
|
}
|
|
|
|
@Override
|
|
public int read(byte[] bytes, int off, int len) throws IOException {
|
|
int read = 0;
|
|
if (backBuf != null && backBuf.length > pos) {
|
|
read = Math.min(len, backBuf.length - pos);
|
|
System.arraycopy(backBuf, pos, bytes, off, read);
|
|
pos += read;
|
|
off += read;
|
|
len -= read;
|
|
}
|
|
if (len > 0) {
|
|
int ar = super.read(bytes, off, len);
|
|
read += ar;
|
|
out.write(bytes, off, ar);
|
|
}
|
|
return read;
|
|
}
|
|
|
|
void unread(byte[] buf) {
|
|
backBuf = buf;
|
|
}
|
|
}
|
|
|
|
public static boolean doSignature(String target, InputStream imgIn, OutputStream imgOut,
|
|
InputStream cert, InputStream key) {
|
|
try {
|
|
PushBackRWStream in = new PushBackRWStream(imgIn, imgOut);
|
|
byte[] hdr = new byte[1024];
|
|
// First read the header
|
|
in.read(hdr);
|
|
int signableSize = getSignableImageSize(hdr);
|
|
// Unread header
|
|
in.unread(hdr);
|
|
BootSignature bootsig = new BootSignature(target, signableSize);
|
|
if (cert == null) {
|
|
cert = SignBoot.class.getResourceAsStream("/keys/verity.x509.pem");
|
|
}
|
|
X509Certificate certificate = CryptoUtils.readCertificate(cert);
|
|
bootsig.setCertificate(certificate);
|
|
if (key == null) {
|
|
key = SignBoot.class.getResourceAsStream("/keys/verity.pk8");
|
|
}
|
|
PrivateKey privateKey = CryptoUtils.readPrivateKey(key);
|
|
byte[] sig = bootsig.sign(privateKey, in, signableSize);
|
|
bootsig.setSignature(sig, CryptoUtils.getSignatureAlgorithmIdentifier(privateKey));
|
|
byte[] encoded_bootsig = bootsig.getEncoded();
|
|
imgOut.write(encoded_bootsig);
|
|
imgOut.flush();
|
|
return true;
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static boolean verifySignature(InputStream imgIn, InputStream certIn) {
|
|
try {
|
|
// Read the header for size
|
|
byte[] hdr = new byte[1024];
|
|
if (imgIn.read(hdr) != hdr.length)
|
|
return false;
|
|
int signableSize = getSignableImageSize(hdr);
|
|
|
|
// Read the rest of the image
|
|
byte[] rawImg = Arrays.copyOf(hdr, signableSize);
|
|
int remain = signableSize - hdr.length;
|
|
if (imgIn.read(rawImg, hdr.length, remain) != remain) {
|
|
System.err.println("Invalid image: not signed");
|
|
return false;
|
|
}
|
|
|
|
// Read footer, which contains the signature
|
|
byte[] signature = new byte[4096];
|
|
imgIn.read(signature);
|
|
|
|
BootSignature bootsig = new BootSignature(signature);
|
|
if (certIn != null) {
|
|
bootsig.setCertificate(CryptoUtils.readCertificate(certIn));
|
|
}
|
|
if (bootsig.verify(rawImg, signableSize)) {
|
|
System.err.println("Signature is VALID");
|
|
return true;
|
|
} else {
|
|
System.err.println("Signature is INVALID");
|
|
}
|
|
} catch (Exception e) {
|
|
System.err.println("Invalid image: not signed");
|
|
}
|
|
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;
|
|
int headerVersion = image.getInt(); // boot image header version or extra size
|
|
if (headerVersion > 0 && headerVersion < 4) {
|
|
image.position(BOOT_IMAGE_HEADER_V1_RECOVERY_DTBO_SIZE_OFFSET);
|
|
int recoveryDtboLength = image.getInt();
|
|
length += ((recoveryDtboLength + pageSize - 1) / pageSize) * pageSize;
|
|
image.getLong(); // recovery_dtbo address
|
|
int headerSize = image.getInt();
|
|
if (headerVersion == 2) {
|
|
image.position(BOOT_IMAGE_HEADER_V2_DTB_SIZE_OFFSET);
|
|
int dtbLength = image.getInt();
|
|
length += ((dtbLength + pageSize - 1) / pageSize) * pageSize;
|
|
image.getLong(); // dtb address
|
|
}
|
|
if (image.position() != headerSize) {
|
|
throw new IllegalArgumentException(
|
|
"Invalid image header: invalid header length");
|
|
}
|
|
} else {
|
|
// headerVersion is 0 or actually extra size in this case
|
|
length += ((headerVersion + 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 algId;
|
|
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);
|
|
this.algId = 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) {
|
|
this.algId = algId;
|
|
signature = new DEROctetString(sig);
|
|
}
|
|
|
|
public void setCertificate(X509Certificate cert)
|
|
throws CertificateEncodingException, IOException {
|
|
ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
|
|
certificate = s.readObject();
|
|
publicKey = cert.getPublicKey();
|
|
}
|
|
|
|
public byte[] sign(PrivateKey key, InputStream is, int len) throws Exception {
|
|
Signature signer = Signature.getInstance(CryptoUtils.getSignatureAlgorithm(key));
|
|
signer.initSign(key);
|
|
int read;
|
|
byte buffer[] = new byte[4096];
|
|
while ((read = is.read(buffer, 0, Math.min(len, buffer.length))) > 0) {
|
|
signer.update(buffer, 0, read);
|
|
len -= read;
|
|
}
|
|
signer.update(getEncodedAuthenticatedAttributes());
|
|
return signer.sign();
|
|
}
|
|
|
|
public boolean verify(byte[] image, int length) throws Exception {
|
|
if (this.length.getValue().intValue() != length) {
|
|
throw new IllegalArgumentException("Invalid image length");
|
|
}
|
|
String algName = CryptoUtils.ID_TO_ALG.get(algId.getAlgorithm().getId());
|
|
if (algName == null) {
|
|
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
|
|
public ASN1Primitive toASN1Primitive() {
|
|
ASN1EncodableVector v = new ASN1EncodableVector();
|
|
v.add(formatVersion);
|
|
v.add(certificate);
|
|
v.add(algId);
|
|
v.add(getAuthenticatedAttributes());
|
|
v.add(signature);
|
|
return new DERSequence(v);
|
|
}
|
|
|
|
}
|
|
}
|