Use the least possible memory for boot signing and verification

Close #971, close #966
This commit is contained in:
topjohnwu 2019-01-16 17:12:23 -05:00
parent 23e5188422
commit 85042fbe25
2 changed files with 84 additions and 32 deletions

View File

@ -4,20 +4,18 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
public class ByteArrayStream extends ByteArrayOutputStream { public class ByteArrayStream extends ByteArrayOutputStream {
public byte[] getBuf() {
return buf;
}
public synchronized void readFrom(InputStream is) { public synchronized void readFrom(InputStream is) {
readFrom(is, Integer.MAX_VALUE); readFrom(is, Integer.MAX_VALUE);
} }
public synchronized void readFrom(InputStream is, int len) { public synchronized void readFrom(InputStream is, int len) {
int read; int read;
byte buffer[] = new byte[4096]; byte buffer[] = new byte[4096];
try { try {
while ((read = is.read(buffer, 0, len < buffer.length ? len : buffer.length)) > 0) { while ((read = is.read(buffer, 0, Math.min(len, buffer.length))) > 0) {
write(buffer, 0, read); write(buffer, 0, read);
len -= read; len -= read;
} }
@ -25,9 +23,7 @@ public class ByteArrayStream extends ByteArrayOutputStream {
e.printStackTrace(); e.printStackTrace();
} }
} }
public synchronized void writeTo(OutputStream out, int off, int len) throws IOException {
out.write(buf, off, len);
}
public ByteArrayInputStream getInputStream() { public ByteArrayInputStream getInputStream() {
return new ByteArrayInputStream(buf, 0, count); return new ByteArrayInputStream(buf, 0, count);
} }

View File

@ -15,6 +15,7 @@ import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -35,20 +36,62 @@ public class SignBoot {
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
} }
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, public static boolean doSignature(String target, InputStream imgIn, OutputStream imgOut,
InputStream cert, InputStream key) { InputStream cert, InputStream key) {
try { try {
ByteArrayStream image = new ByteArrayStream(); PushBackRWStream in = new PushBackRWStream(imgIn, imgOut);
image.readFrom(imgIn); byte[] hdr = new byte[1024];
int signableSize = getSignableImageSize(image.getBuf()); // First read the header
if (signableSize < image.size()) { in.read(hdr);
System.err.println("NOTE: truncating input from " + int signableSize = getSignableImageSize(hdr);
image.size() + " to " + signableSize + " bytes"); // Unread header
} else if (signableSize > image.size()) { in.unread(hdr);
throw new IllegalArgumentException("Invalid image: too short, expected " + BootSignature bootsig = new BootSignature(target, signableSize);
signableSize + " bytes");
}
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");
} }
@ -58,10 +101,9 @@ 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(privateKey, image.getBuf(), signableSize), byte[] sig = bootsig.sign(privateKey, in, signableSize);
CryptoUtils.getSignatureAlgorithmIdentifier(privateKey)); bootsig.setSignature(sig, CryptoUtils.getSignatureAlgorithmIdentifier(privateKey));
byte[] encoded_bootsig = bootsig.getEncoded(); byte[] encoded_bootsig = bootsig.getEncoded();
image.writeTo(imgOut);
imgOut.write(encoded_bootsig); imgOut.write(encoded_bootsig);
imgOut.flush(); imgOut.flush();
return true; return true;
@ -73,19 +115,29 @@ public class SignBoot {
public static boolean verifySignature(InputStream imgIn, InputStream certIn) { public static boolean verifySignature(InputStream imgIn, InputStream certIn) {
try { try {
ByteArrayStream image = new ByteArrayStream(); // Read the header for size
image.readFrom(imgIn); byte[] hdr = new byte[1024];
int signableSize = getSignableImageSize(image.getBuf()); if (imgIn.read(hdr) != hdr.length)
if (signableSize >= image.size()) { 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"); System.err.println("Invalid image: not signed");
return false; return false;
} }
byte[] signature = Arrays.copyOfRange(image.getBuf(), signableSize, image.size());
// Read footer, which contains the signature
byte[] signature = new byte[4096];
imgIn.read(signature);
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(image.getBuf(), signableSize)) { if (bootsig.verify(rawImg, signableSize)) {
System.err.println("Signature is VALID"); System.err.println("Signature is VALID");
return true; return true;
} else { } else {
@ -148,8 +200,7 @@ public class SignBoot {
* Initializes the object for verifying a signed image file * Initializes the object for verifying a signed image file
* @param signature Signature footer * @param signature Signature footer
*/ */
public BootSignature(byte[] signature) public BootSignature(byte[] signature) throws Exception {
throws Exception {
ASN1InputStream stream = new ASN1InputStream(signature); ASN1InputStream stream = new ASN1InputStream(signature);
ASN1Sequence sequence = (ASN1Sequence) stream.readObject(); ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
formatVersion = (ASN1Integer) sequence.getObjectAt(0); formatVersion = (ASN1Integer) sequence.getObjectAt(0);
@ -193,10 +244,15 @@ public class SignBoot {
publicKey = cert.getPublicKey(); publicKey = cert.getPublicKey();
} }
public byte[] sign(PrivateKey key, byte[] image, int length) throws Exception { public byte[] sign(PrivateKey key, InputStream is, int len) throws Exception {
Signature signer = Signature.getInstance(CryptoUtils.getSignatureAlgorithm(key)); Signature signer = Signature.getInstance(CryptoUtils.getSignatureAlgorithm(key));
signer.initSign(key); signer.initSign(key);
signer.update(image, 0, length); 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()); signer.update(getEncodedAuthenticatedAttributes());
return signer.sign(); return signer.sign();
} }