Merge tag android-5.1.0_r1 into AOSP_5.1_MERGE
Change-Id: I3c99bf4440205917238a64d91d5ae07e558b9eac
diff --git a/ext4_utils/mkuserimg.sh b/ext4_utils/mkuserimg.sh
index be69738..78936e0 100755
--- a/ext4_utils/mkuserimg.sh
+++ b/ext4_utils/mkuserimg.sh
@@ -5,7 +5,7 @@
function usage() {
cat<<EOT
Usage:
-mkuserimg.sh [-s] SRC_DIR OUTPUT_FILE EXT_VARIANT MOUNT_POINT SIZE
+mkuserimg.sh [-s] SRC_DIR OUTPUT_FILE EXT_VARIANT MOUNT_POINT SIZE [-j <journal_size>]
[-T TIMESTAMP] [-C FS_CONFIG] [-B BLOCK_LIST_FILE] [FILE_CONTEXTS]
EOT
}
@@ -33,6 +33,16 @@
SIZE=$5
shift; shift; shift; shift; shift
+JOURNAL_FLAGS=
+if [ "$1" = "-j" ]; then
+ if [ "$2" = "0" ]; then
+ JOURNAL_FLAGS="-J"
+ else
+ JOURNAL_FLAGS="-j $2"
+ fi
+ shift; shift
+fi
+
TIMESTAMP=-1
if [[ "$1" == "-T" ]]; then
TIMESTAMP=$2
@@ -79,7 +89,7 @@
OPT="$OPT -B $BLOCK_LIST"
fi
-MAKE_EXT4FS_CMD="make_ext4fs $ENABLE_SPARSE_IMAGE -T $TIMESTAMP $OPT -l $SIZE -a $MOUNT_POINT $OUTPUT_FILE $SRC_DIR"
+MAKE_EXT4FS_CMD="make_ext4fs $ENABLE_SPARSE_IMAGE -T $TIMESTAMP $OPT -l $SIZE $JOURNAL_FLAGS -a $MOUNT_POINT $OUTPUT_FILE $SRC_DIR"
echo $MAKE_EXT4FS_CMD
vmstat 1 &
statpid=$!
diff --git a/verity/Android.mk b/verity/Android.mk
index ab1659b..f166f9e 100644
--- a/verity/Android.mk
+++ b/verity/Android.mk
@@ -1,6 +1,15 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
+LOCAL_MODULE := verify_boot_signature
+LOCAL_SRC_FILES := verify_boot_signature.c
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+LOCAL_SHARED_LIBRARIES := libcrypto-host
+LOCAL_C_INCLUDES += external/openssl/include system/extras/ext4_utils system/core/mkbootimg
+include $(BUILD_HOST_EXECUTABLE)
+
+include $(CLEAR_VARS)
LOCAL_MODULE := generate_verity_key
LOCAL_SRC_FILES := generate_verity_key.c
LOCAL_MODULE_CLASS := EXECUTABLES
@@ -10,6 +19,14 @@
include $(BUILD_HOST_EXECUTABLE)
include $(CLEAR_VARS)
+LOCAL_SRC_FILES := VerityVerifier.java Utils.java
+LOCAL_MODULE := VerityVerifier
+LOCAL_JAR_MANIFEST := VerityVerifier.mf
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
LOCAL_SRC_FILES := VeritySigner.java Utils.java
LOCAL_MODULE := VeritySigner
LOCAL_JAR_MANIFEST := VeritySigner.mf
@@ -34,6 +51,15 @@
include $(BUILD_HOST_JAVA_LIBRARY)
include $(CLEAR_VARS)
+LOCAL_SRC_FILES := verity_verifier
+LOCAL_MODULE := verity_verifier
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_REQUIRED_MODULES := VerityVerifier
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
LOCAL_SRC_FILES := verity_signer
LOCAL_MODULE := verity_signer
LOCAL_MODULE_CLASS := EXECUTABLES
diff --git a/verity/BootSignature.java b/verity/BootSignature.java
index f5ceb30..03eb32a 100644
--- a/verity/BootSignature.java
+++ b/verity/BootSignature.java
@@ -16,51 +16,105 @@
package com.android.verity;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateEncodingException;
import java.util.Arrays;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.util.ASN1Dump;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* AndroidVerifiedBootSignature DEFINITIONS ::=
* BEGIN
- * FormatVersion ::= INTEGER
- * AlgorithmIdentifier ::= SEQUENCE {
+ * formatVersion ::= INTEGER
+ * certificate ::= Certificate
+ * algorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER,
* parameters ANY DEFINED BY algorithm OPTIONAL
* }
- * AuthenticatedAttributes ::= SEQUENCE {
+ * authenticatedAttributes ::= SEQUENCE {
* target CHARACTER STRING,
* length INTEGER
* }
- * Signature ::= OCTET STRING
+ * signature ::= OCTET STRING
* END
*/
public 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(0);
+ this.formatVersion = new ASN1Integer(FORMAT_VERSION);
this.target = new DERPrintableString(target);
this.length = new ASN1Integer(length);
- this.algorithmIdentifier = new AlgorithmIdentifier(
- PKCSObjectIdentifiers.sha256WithRSAEncryption);
+ }
+
+ /**
+ * 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() {
@@ -74,10 +128,29 @@
return getAuthenticatedAttributes().getEncoded();
}
- public void setSignature(byte[] sig) {
+ public AlgorithmIdentifier getAlgorithmIdentifier() {
+ return algorithmIdentifier;
+ }
+
+ public PublicKey getPublicKey() {
+ return publicKey;
+ }
+
+ public byte[] getSignature() {
+ return signature.getOctets();
+ }
+
+ 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();
+ }
+
public byte[] generateSignableImage(byte[] image) throws IOException {
byte[] attrs = getEncodedAuthenticatedAttributes();
byte[] signable = Arrays.copyOf(image, image.length + attrs.length);
@@ -89,36 +162,145 @@
public byte[] sign(byte[] image, PrivateKey key) throws Exception {
byte[] signable = generateSignableImage(image);
- byte[] signature = Utils.sign(key, signable);
- byte[] signed = Arrays.copyOf(image, image.length + signature.length);
- for (int i=0; i < signature.length; i++) {
- signed[i+image.length] = signature[i];
+ return Utils.sign(key, signable);
+ }
+
+ public boolean verify(byte[] image) throws Exception {
+ if (length.getValue().intValue() != image.length) {
+ throw new IllegalArgumentException("Invalid image length");
}
- return signed;
+
+ byte[] signable = generateSignableImage(image);
+ return Utils.verify(publicKey, signable, signature.getOctets(),
+ algorithmIdentifier);
}
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);
}
+ 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;
+ }
+
public static void doSignature( String target,
String imagePath,
String keyPath,
+ String certPath,
String outPath) throws Exception {
+
byte[] image = Utils.read(imagePath);
+ int signableSize = getSignableImageSize(image);
+
+ if (signableSize < image.length) {
+ System.err.println("NOTE: truncating file " + imagePath +
+ " 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);
- PrivateKey key = Utils.loadPEMPrivateKeyFromFile(keyPath);
- byte[] signature = bootsig.sign(image, key);
- Utils.write(signature, outPath);
+
+ X509Certificate cert = Utils.loadPEMCertificate(certPath);
+ bootsig.setCertificate(cert);
+
+ PrivateKey key = Utils.loadDERPrivateKeyFromFile(keyPath);
+ bootsig.setSignature(bootsig.sign(image, key),
+ Utils.getSignatureAlgorithmIdentifier(key));
+
+ byte[] encoded_bootsig = bootsig.getEncoded();
+ byte[] image_with_metadata = Arrays.copyOf(image, image.length + encoded_bootsig.length);
+
+ System.arraycopy(encoded_bootsig, 0, image_with_metadata,
+ image.length, encoded_bootsig.length);
+
+ Utils.write(image_with_metadata, outPath);
}
- // java -cp ../../../out/host/common/obj/JAVA_LIBRARIES/AndroidVerifiedBootSigner_intermediates/classes/ com.android.verity.AndroidVerifiedBootSigner boot ../../../out/target/product/flounder/boot.img ../../../build/target/product/security/verity_private_dev_key /tmp/boot.img.signed
- public static void main(String[] args) throws Exception {
- doSignature(args[0], args[1], args[2], args[3]);
+ public static void verifySignature(String imagePath) throws Exception {
+ byte[] image = Utils.read(imagePath);
+ int signableSize = getSignableImageSize(image);
+
+ if (signableSize >= image.length) {
+ throw new IllegalArgumentException("Invalid image: not signed");
+ }
+
+ byte[] signature = Arrays.copyOfRange(image, signableSize, image.length);
+ BootSignature bootsig = new BootSignature(signature);
+
+ try {
+ if (bootsig.verify(Arrays.copyOf(image, signableSize))) {
+ System.err.println("Signature is VALID");
+ System.exit(0);
+ } else {
+ System.err.println("Signature is INVALID");
+ }
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ }
+ System.exit(1);
}
-}
\ No newline at end of file
+
+ /* Example usage for signing a boot image using dev keys:
+ java -cp \
+ ../../../out/host/common/obj/JAVA_LIBRARIES/BootSignature_intermediates/ \
+ classes/com.android.verity.BootSignature \
+ /boot \
+ ../../../out/target/product/$PRODUCT/boot.img \
+ ../../../build/target/product/security/verity.pk8 \
+ ../../../build/target/product/security/verity.x509.pem \
+ /tmp/boot.img.signed
+ */
+ public static void main(String[] args) throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if ("-verify".equals(args[0])) {
+ /* args[1] is the path to a signed boot image */
+ verifySignature(args[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 to a private key
+ args[3] is the path to the matching public key certificate
+ args[4] is the path where to output the signed boot image
+ */
+ doSignature(args[0], args[1], args[2], args[3], args[4]);
+ }
+ }
+}
diff --git a/verity/KeystoreSigner.java b/verity/KeystoreSigner.java
index d57f328..0927d54 100644
--- a/verity/KeystoreSigner.java
+++ b/verity/KeystoreSigner.java
@@ -19,12 +19,17 @@
import java.io.IOException;
import java.security.PrivateKey;
import java.security.PublicKey;
+import java.security.Security;
import java.security.Signature;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
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.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.DERSequence;
@@ -32,6 +37,7 @@
import org.bouncycastle.asn1.pkcs.RSAPublicKey;
import org.bouncycastle.asn1.util.ASN1Dump;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* AndroidVerifiedBootKeystore DEFINITIONS ::=
@@ -61,8 +67,7 @@
this.keyMaterial = new RSAPublicKey(
k.getModulus(),
k.getPublicExponent());
- this.algorithmIdentifier = new AlgorithmIdentifier(
- PKCSObjectIdentifiers.sha256WithRSAEncryption);
+ this.algorithmIdentifier = Utils.getSignatureAlgorithmIdentifier(key);
}
public ASN1Primitive toASN1Primitive() {
@@ -79,12 +84,15 @@
class BootKeystore extends ASN1Object
{
- private ASN1Integer formatVersion;
- private ASN1EncodableVector keyBag;
- private BootSignature signature;
+ private ASN1Integer formatVersion;
+ private ASN1EncodableVector keyBag;
+ private BootSignature signature;
+ private X509Certificate certificate;
+
+ private static final int FORMAT_VERSION = 0;
public BootKeystore() {
- this.formatVersion = new ASN1Integer(0);
+ this.formatVersion = new ASN1Integer(FORMAT_VERSION);
this.keyBag = new ASN1EncodableVector();
}
@@ -94,6 +102,10 @@
keyBag.add(k);
}
+ public void setCertificate(X509Certificate cert) {
+ certificate = cert;
+ }
+
public byte[] getInnerKeystore() throws Exception {
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(formatVersion);
@@ -109,29 +121,87 @@
return new DERSequence(v);
}
+ public void parse(byte[] input) throws Exception {
+ ASN1InputStream stream = new ASN1InputStream(input);
+ ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
+
+ formatVersion = (ASN1Integer) sequence.getObjectAt(0);
+ if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
+ throw new IllegalArgumentException("Unsupported format version");
+ }
+
+ ASN1Sequence keys = (ASN1Sequence) sequence.getObjectAt(1);
+ Enumeration e = keys.getObjects();
+ while (e.hasMoreElements()) {
+ keyBag.add((ASN1Encodable) e.nextElement());
+ }
+
+ ASN1Object sig = sequence.getObjectAt(2).toASN1Primitive();
+ signature = new BootSignature(sig.getEncoded());
+ }
+
+ public boolean verify() throws Exception {
+ byte[] innerKeystore = getInnerKeystore();
+ return Utils.verify(signature.getPublicKey(), innerKeystore,
+ signature.getSignature(), signature.getAlgorithmIdentifier());
+ }
+
public void sign(PrivateKey privateKey) throws Exception {
byte[] innerKeystore = getInnerKeystore();
byte[] rawSignature = Utils.sign(privateKey, innerKeystore);
signature = new BootSignature("keystore", innerKeystore.length);
- signature.setSignature(rawSignature);
+ signature.setCertificate(certificate);
+ signature.setSignature(rawSignature,
+ Utils.getSignatureAlgorithmIdentifier(privateKey));
}
public void dump() throws Exception {
System.out.println(ASN1Dump.dumpAsString(toASN1Primitive()));
}
- // USAGE:
- // AndroidVerifiedBootKeystoreSigner <privkeyFile> <outfile> <pubkeyFile0> ... <pubkeyFileN-1>
- // EG:
- // java -cp ../../../out/host/common/obj/JAVA_LIBRARIES/AndroidVerifiedBootKeystoreSigner_intermediates/classes/ com.android.verity.AndroidVerifiedBootKeystoreSigner ../../../build/target/product/security/verity_private_dev_key /tmp/keystore.out /tmp/k
- public static void main(String[] args) throws Exception {
- String privkeyFname = args[0];
- String outfileFname = args[1];
- BootKeystore ks = new BootKeystore();
- for (int i=2; i < args.length; i++) {
- ks.addPublicKey(Utils.read(args[i]));
- }
- ks.sign(Utils.loadPEMPrivateKeyFromFile(privkeyFname));
- Utils.write(ks.getEncoded(), outfileFname);
+ private static void usage() {
+ System.err.println("usage: KeystoreSigner <privatekey.pk8> " +
+ "<certificate.x509.pem> <outfile> <publickey0.der> " +
+ "... <publickeyN-1.der> | -verify <keystore>");
+ System.exit(1);
}
-}
\ No newline at end of file
+
+ public static void main(String[] args) throws Exception {
+ if (args.length < 2) {
+ usage();
+ return;
+ }
+
+ Security.addProvider(new BouncyCastleProvider());
+ BootKeystore ks = new BootKeystore();
+
+ if ("-verify".equals(args[0])) {
+ ks.parse(Utils.read(args[1]));
+
+ try {
+ if (ks.verify()) {
+ System.err.println("Signature is VALID");
+ System.exit(0);
+ } else {
+ System.err.println("Signature is INVALID");
+ }
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ }
+ System.exit(1);
+ } else {
+ String privkeyFname = args[0];
+ String certFname = args[1];
+ String outfileFname = args[2];
+
+ ks.setCertificate(Utils.loadPEMCertificate(certFname));
+
+ for (int i = 3; i < args.length; i++) {
+ ks.addPublicKey(Utils.read(args[i]));
+ }
+
+ ks.sign(Utils.loadDERPrivateKeyFromFile(privkeyFname));
+ Utils.write(ks.getEncoded(), outfileFname);
+ }
+ }
+}
diff --git a/verity/KeystoreSigner.mf b/verity/KeystoreSigner.mf
index a4fee27..472b7c4 100644
--- a/verity/KeystoreSigner.mf
+++ b/verity/KeystoreSigner.mf
@@ -1 +1 @@
-Main-Class: com.android.verity.KeystoreSigner
\ No newline at end of file
+Main-Class: com.android.verity.BootKeystore
diff --git a/verity/Utils.java b/verity/Utils.java
index 2c1e7bb..3576e3b 100644
--- a/verity/Utils.java
+++ b/verity/Utils.java
@@ -18,22 +18,60 @@
import java.lang.reflect.Constructor;
import java.io.File;
+import java.io.ByteArrayInputStream;
+import java.io.Console;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.InputStreamReader;
import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.KeyFactory;
import java.security.Provider;
import java.security.Security;
import java.security.Signature;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
import java.security.spec.X509EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import javax.crypto.Cipher;
+import javax.crypto.EncryptedPrivateKeyInfo;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.util.encoders.Base64;
public class Utils {
+ private static final Map<String, String> ID_TO_ALG;
+ private static final Map<String, String> ALG_TO_ID;
+
+ static {
+ ID_TO_ALG = new HashMap<String, String>();
+ ALG_TO_ID = new HashMap<String, String>();
+
+ 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("SHA1withRSA", PKCSObjectIdentifiers.sha1WithRSAEncryption.getId());
+ ALG_TO_ID.put("SHA256withRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption.getId());
+ ALG_TO_ID.put("SHA512withRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption.getId());
+ }
+
private static void loadProviderIfNecessary(String providerClassName) {
if (providerClassName == null) {
return;
@@ -88,10 +126,45 @@
return Base64.decode(base64_der);
}
+ private static PKCS8EncodedKeySpec decryptPrivateKey(byte[] encryptedPrivateKey)
+ throws GeneralSecurityException {
+ EncryptedPrivateKeyInfo epkInfo;
+ try {
+ epkInfo = new EncryptedPrivateKeyInfo(encryptedPrivateKey);
+ } catch (IOException ex) {
+ // Probably not an encrypted key.
+ return null;
+ }
+
+ char[] password = System.console().readPassword("Password for the private key file: ");
+
+ SecretKeyFactory skFactory = SecretKeyFactory.getInstance(epkInfo.getAlgName());
+ Key key = skFactory.generateSecret(new PBEKeySpec(password));
+ Arrays.fill(password, '\0');
+
+ Cipher cipher = Cipher.getInstance(epkInfo.getAlgName());
+ cipher.init(Cipher.DECRYPT_MODE, key, epkInfo.getAlgParameters());
+
+ try {
+ return epkInfo.getKeySpec(cipher);
+ } catch (InvalidKeySpecException ex) {
+ System.err.println("Password may be bad.");
+ throw ex;
+ }
+ }
+
static PrivateKey loadDERPrivateKey(byte[] der) throws Exception {
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(der);
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- return (PrivateKey) keyFactory.generatePrivate(keySpec);
+ PKCS8EncodedKeySpec spec = decryptPrivateKey(der);
+
+ if (spec == null) {
+ spec = new PKCS8EncodedKeySpec(der);
+ }
+
+ 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);
}
static PrivateKey loadPEMPrivateKey(byte[] pem) throws Exception {
@@ -128,8 +201,48 @@
return loadDERPublicKey(read(keyFname));
}
+ static X509Certificate loadPEMCertificate(String fname) throws Exception {
+ try (FileInputStream fis = new FileInputStream(fname)) {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) cf.generateCertificate(fis);
+ }
+ }
+
+ private static String getSignatureAlgorithm(Key key) {
+ if ("RSA".equals(key.getAlgorithm())) {
+ return "SHA256withRSA";
+ } else {
+ throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
+ }
+ }
+
+ static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) {
+ 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.getObjectId().getId());
+
+ if (algName == null) {
+ throw new IllegalArgumentException("Unsupported algorithm " + algId.getObjectId());
+ }
+
+ 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("SHA1withRSA");
+ Signature signer = Signature.getInstance(getSignatureAlgorithm(privateKey));
signer.initSign(privateKey);
signer.update(input);
return signer.sign();
@@ -153,4 +266,4 @@
out.write(data);
out.close();
}
-}
\ No newline at end of file
+}
diff --git a/verity/VeritySigner.java b/verity/VeritySigner.java
index 44c5602..9d85747 100644
--- a/verity/VeritySigner.java
+++ b/verity/VeritySigner.java
@@ -16,18 +16,54 @@
package com.android.verity;
+import java.security.PublicKey;
import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class VeritySigner {
- // USAGE:
- // VeritySigner <contentfile> <key.pem> <sigfile>
- // To verify that this has correct output:
- // openssl rsautl -raw -inkey <key.pem> -encrypt -in <sigfile> > /tmp/dump
+ private static void usage() {
+ System.err.println("usage: VeritySigner <contentfile> <key.pk8> " +
+ "<sigfile> | <contentfile> <certificate.x509.pem> <sigfile> " +
+ "-verify");
+ System.exit(1);
+ }
+
public static void main(String[] args) throws Exception {
+ if (args.length < 3) {
+ usage();
+ return;
+ }
+
+ Security.addProvider(new BouncyCastleProvider());
+
byte[] content = Utils.read(args[0]);
- PrivateKey privateKey = Utils.loadPEMPrivateKey(Utils.read(args[1]));
- byte[] signature = Utils.sign(privateKey, content);
- Utils.write(signature, args[2]);
+
+ if (args.length > 3 && "-verify".equals(args[3])) {
+ X509Certificate cert = Utils.loadPEMCertificate(args[1]);
+ PublicKey publicKey = cert.getPublicKey();
+
+ byte[] signature = Utils.read(args[2]);
+
+ try {
+ if (Utils.verify(publicKey, content, signature,
+ Utils.getSignatureAlgorithmIdentifier(publicKey))) {
+ System.err.println("Signature is VALID");
+ System.exit(0);
+ } else {
+ System.err.println("Signature is INVALID");
+ }
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ }
+
+ System.exit(1);
+ } else {
+ PrivateKey privateKey = Utils.loadDERPrivateKey(Utils.read(args[1]));
+ byte[] signature = Utils.sign(privateKey, content);
+ Utils.write(signature, args[2]);
+ }
}
}
diff --git a/verity/VerityVerifier.java b/verity/VerityVerifier.java
new file mode 100644
index 0000000..5c9d7d2
--- /dev/null
+++ b/verity/VerityVerifier.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.verity;
+
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.lang.Process;
+import java.lang.Runtime;
+import java.security.PublicKey;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class VerityVerifier {
+
+ private static final int EXT4_SB_MAGIC = 0xEF53;
+ private static final int EXT4_SB_OFFSET = 0x400;
+ private static final int EXT4_SB_OFFSET_MAGIC = EXT4_SB_OFFSET + 0x38;
+ private static final int EXT4_SB_OFFSET_LOG_BLOCK_SIZE = EXT4_SB_OFFSET + 0x18;
+ private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_LO = EXT4_SB_OFFSET + 0x4;
+ private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_HI = EXT4_SB_OFFSET + 0x150;
+ private static final int VERITY_MAGIC = 0xB001B001;
+ private static final int VERITY_SIGNATURE_SIZE = 256;
+ private static final int VERITY_VERSION = 0;
+
+ /**
+ * Converts a 4-byte little endian value to a Java integer
+ * @param value Little endian integer to convert
+ */
+ public static int fromle(int value) {
+ byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
+ return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
+ }
+
+ /**
+ * Converts a 2-byte little endian value to Java a integer
+ * @param value Little endian short to convert
+ */
+ public static int fromle(short value) {
+ return fromle(value << 16);
+ }
+
+ /**
+ * Unsparses a sparse image into a temporary file and returns a
+ * handle to the file
+ * @param fname Path to a sparse image file
+ */
+ public static RandomAccessFile openImage(String fname) throws Exception {
+ File tmp = File.createTempFile("system", ".raw");
+ tmp.deleteOnExit();
+
+ Process p = Runtime.getRuntime().exec("simg2img " + fname +
+ " " + tmp.getAbsoluteFile());
+
+ p.waitFor();
+ if (p.exitValue() != 0) {
+ throw new IllegalArgumentException("Invalid image: failed to unsparse");
+ }
+
+ return new RandomAccessFile(tmp, "r");
+ }
+
+ /**
+ * Reads the ext4 superblock and calculates the size of the system image,
+ * after which we should find the verity metadata
+ * @param img File handle to the image file
+ */
+ public static long getMetadataPosition(RandomAccessFile img)
+ throws Exception {
+ img.seek(EXT4_SB_OFFSET_MAGIC);
+ int magic = fromle(img.readShort());
+
+ if (magic != EXT4_SB_MAGIC) {
+ throw new IllegalArgumentException("Invalid image: not a valid ext4 image");
+ }
+
+ img.seek(EXT4_SB_OFFSET_BLOCKS_COUNT_LO);
+ long blocksCountLo = fromle(img.readInt());
+
+ img.seek(EXT4_SB_OFFSET_LOG_BLOCK_SIZE);
+ long logBlockSize = fromle(img.readInt());
+
+ img.seek(EXT4_SB_OFFSET_BLOCKS_COUNT_HI);
+ long blocksCountHi = fromle(img.readInt());
+
+ long blockSizeBytes = 1L << (10 + logBlockSize);
+ long blockCount = (blocksCountHi << 32) + blocksCountLo;
+ return blockSizeBytes * blockCount;
+ }
+
+ /**
+ * Reads and validates verity metadata, and check the signature against the
+ * given public key
+ * @param img File handle to the image file
+ * @param key Public key to use for signature verification
+ */
+ public static boolean verifyMetaData(RandomAccessFile img, PublicKey key)
+ throws Exception {
+ img.seek(getMetadataPosition(img));
+ int magic = fromle(img.readInt());
+
+ if (magic != VERITY_MAGIC) {
+ throw new IllegalArgumentException("Invalid image: verity metadata not found");
+ }
+
+ int version = fromle(img.readInt());
+
+ if (version != VERITY_VERSION) {
+ throw new IllegalArgumentException("Invalid image: unknown metadata version");
+ }
+
+ byte[] signature = new byte[VERITY_SIGNATURE_SIZE];
+ img.readFully(signature);
+
+ int tableSize = fromle(img.readInt());
+
+ byte[] table = new byte[tableSize];
+ img.readFully(table);
+
+ return Utils.verify(key, table, signature,
+ Utils.getSignatureAlgorithmIdentifier(key));
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length != 2) {
+ System.err.println("Usage: VerityVerifier <sparse.img> <certificate.x509.pem>");
+ System.exit(1);
+ }
+
+ Security.addProvider(new BouncyCastleProvider());
+
+ X509Certificate cert = Utils.loadPEMCertificate(args[1]);
+ PublicKey key = cert.getPublicKey();
+ RandomAccessFile img = openImage(args[0]);
+
+ try {
+ if (verifyMetaData(img, key)) {
+ System.err.println("Signature is VALID");
+ System.exit(0);
+ } else {
+ System.err.println("Signature is INVALID");
+ }
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ }
+
+ System.exit(1);
+ }
+}
diff --git a/verity/VerityVerifier.mf b/verity/VerityVerifier.mf
new file mode 100644
index 0000000..6118b31
--- /dev/null
+++ b/verity/VerityVerifier.mf
@@ -0,0 +1 @@
+Main-Class: com.android.verity.VerityVerifier
diff --git a/verity/generate_verity_key.c b/verity/generate_verity_key.c
index 7414af5..a55600c 100644
--- a/verity/generate_verity_key.c
+++ b/verity/generate_verity_key.c
@@ -108,6 +108,66 @@
return ret;
}
+static int convert_x509(const char *pem_file, const char *key_file)
+{
+ int ret = -1;
+ FILE *f = NULL;
+ EVP_PKEY *pkey = NULL;
+ RSA *rsa = NULL;
+ X509 *cert = NULL;
+
+ if (!pem_file || !key_file) {
+ goto out;
+ }
+
+ f = fopen(pem_file, "r");
+ if (!f) {
+ printf("Failed to open '%s'\n", pem_file);
+ goto out;
+ }
+
+ cert = PEM_read_X509(f, &cert, NULL, NULL);
+ if (!cert) {
+ printf("Failed to read PEM certificate from file '%s'\n", pem_file);
+ goto out;
+ }
+
+ pkey = X509_get_pubkey(cert);
+ if (!pkey) {
+ printf("Failed to extract public key from certificate '%s'\n", pem_file);
+ goto out;
+ }
+
+ rsa = EVP_PKEY_get1_RSA(pkey);
+ if (!rsa) {
+ printf("Failed to get the RSA public key from '%s'\n", pem_file);
+ goto out;
+ }
+
+ if (write_public_keyfile(rsa, key_file) < 0) {
+ printf("Failed to write public key\n");
+ goto out;
+ }
+
+ ret = 0;
+
+out:
+ if (f) {
+ fclose(f);
+ }
+ if (cert) {
+ X509_free(cert);
+ }
+ if (pkey) {
+ EVP_PKEY_free(pkey);
+ }
+ if (rsa) {
+ RSA_free(rsa);
+ }
+
+ return ret;
+}
+
static int generate_key(const char *file)
{
int ret = -1;
@@ -153,13 +213,16 @@
}
static void usage(){
- printf("Usage: generate_verity_key <path-to-key>");
+ printf("Usage: generate_verity_key <path-to-key> | -convert <path-to-x509-pem> <path-to-key>\n");
}
int main(int argc, char *argv[]) {
- if (argc != 2) {
+ if (argc == 2) {
+ return generate_key(argv[1]);
+ } else if (argc == 4 && !strcmp(argv[1], "-convert")) {
+ return convert_x509(argv[2], argv[3]);
+ } else {
usage();
exit(-1);
}
- return generate_key(argv[1]);
-}
\ No newline at end of file
+}
diff --git a/verity/keystore_signer b/verity/keystore_signer
old mode 100644
new mode 100755
index 7619c54..445f0c9
--- a/verity/keystore_signer
+++ b/verity/keystore_signer
@@ -5,4 +5,4 @@
KEYSTORESIGNER_HOME=`dirname "$0"`
KEYSTORESIGNER_HOME=`dirname "$KEYSTORESIGNER_HOME"`
-java -Xmx512M -jar "$KEYSTORESIGNER_HOME"/framework/KeystoreSigner.jar "$@"
\ No newline at end of file
+java -Xmx512M -jar "$KEYSTORESIGNER_HOME"/framework/BootKeystoreSigner.jar "$@"
diff --git a/verity/verify_boot_signature.c b/verity/verify_boot_signature.c
new file mode 100644
index 0000000..2274291
--- /dev/null
+++ b/verity/verify_boot_signature.c
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _LARGEFILE64_SOURCE
+
+#include <endian.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/rsa.h>
+#include <openssl/x509.h>
+
+#include "bootimg.h"
+
+#define FORMAT_VERSION 1
+#define BUFFER_SIZE (1024 * 1024)
+
+typedef struct {
+ ASN1_STRING *target;
+ ASN1_INTEGER *length;
+} AuthAttrs;
+
+ASN1_SEQUENCE(AuthAttrs) = {
+ ASN1_SIMPLE(AuthAttrs, target, ASN1_PRINTABLE),
+ ASN1_SIMPLE(AuthAttrs, length, ASN1_INTEGER)
+} ASN1_SEQUENCE_END(AuthAttrs)
+
+IMPLEMENT_ASN1_FUNCTIONS(AuthAttrs)
+
+typedef struct {
+ ASN1_INTEGER *formatVersion;
+ X509 *certificate;
+ X509_ALGOR *algorithmIdentifier;
+ AuthAttrs *authenticatedAttributes;
+ ASN1_OCTET_STRING *signature;
+} BootSignature;
+
+ASN1_SEQUENCE(BootSignature) = {
+ ASN1_SIMPLE(BootSignature, formatVersion, ASN1_INTEGER),
+ ASN1_SIMPLE(BootSignature, certificate, X509),
+ ASN1_SIMPLE(BootSignature, algorithmIdentifier, X509_ALGOR),
+ ASN1_SIMPLE(BootSignature, authenticatedAttributes, AuthAttrs),
+ ASN1_SIMPLE(BootSignature, signature, ASN1_OCTET_STRING)
+} ASN1_SEQUENCE_END(BootSignature)
+
+IMPLEMENT_ASN1_FUNCTIONS(BootSignature)
+
+static BIO *g_error = NULL;
+
+/**
+ * Rounds n up to the nearest multiple of page_size
+ * @param n The value to round
+ * @param page_size Page size
+ */
+static uint64_t page_align(uint64_t n, uint64_t page_size)
+{
+ return (((n + page_size - 1) / page_size) * page_size);
+}
+
+/**
+ * Calculates the offset to the beginning of the BootSignature block
+ * based on the boot image header. The signature will start after the
+ * the boot image contents.
+ * @param fd File descriptor to the boot image
+ * @param offset Receives the offset in bytes
+ */
+static int get_signature_offset(int fd, off64_t *offset)
+{
+ int i;
+ struct boot_img_hdr hdr;
+
+ if (!offset) {
+ return -1;
+ }
+
+ if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+ return -1;
+ }
+
+ if (memcmp(BOOT_MAGIC, hdr.magic, BOOT_MAGIC_SIZE) != 0) {
+ printf("Invalid boot image: missing magic\n");
+ return -1;
+ }
+
+ if (!hdr.page_size) {
+ printf("Invalid boot image: page size must be non-zero\n");
+ return -1;
+ }
+
+ *offset = page_align(hdr.page_size
+ + page_align(hdr.kernel_size, hdr.page_size)
+ + page_align(hdr.ramdisk_size, hdr.page_size)
+ + page_align(hdr.second_size, hdr.page_size),
+ hdr.page_size);
+
+ return 0;
+}
+
+/**
+ * Reads and parses the ASN.1 BootSignature block from the given offset
+ * @param fd File descriptor to the boot image
+ * @param offset Offset from the beginning of file to the signature
+ * @param bs Pointer to receive the BootImage structure
+ */
+static int read_signature(int fd, off64_t offset, BootSignature **bs)
+{
+ BIO *in = NULL;
+
+ if (!bs) {
+ return -1;
+ }
+
+ if (lseek64(fd, offset, SEEK_SET) == -1) {
+ return -1;
+ }
+
+ if ((in = BIO_new_fd(fd, BIO_NOCLOSE)) == NULL) {
+ ERR_print_errors(g_error);
+ return -1;
+ }
+
+ if ((*bs = ASN1_item_d2i_bio(ASN1_ITEM_rptr(BootSignature), in, bs)) == NULL) {
+ ERR_print_errors(g_error);
+ BIO_free(in);
+ return -1;
+ }
+
+ BIO_free(in);
+ return 0;
+}
+
+/**
+ * Validates the format of the boot signature block, and checks that
+ * the length in authenticated attributes matches the actual length of
+ * the image.
+ * @param bs The boot signature block to validate
+ * @param length The actual length of the boot image without the signature
+ */
+static int validate_signature_block(const BootSignature *bs, uint64_t length)
+{
+ BIGNUM expected;
+ BIGNUM value;
+ int rc = -1;
+
+ if (!bs) {
+ return -1;
+ }
+
+ BN_init(&expected);
+ BN_init(&value);
+
+ /* Confirm that formatVersion matches our supported version */
+ if (!BN_set_word(&expected, FORMAT_VERSION)) {
+ ERR_print_errors(g_error);
+ goto vsb_done;
+ }
+
+ ASN1_INTEGER_to_BN(bs->formatVersion, &value);
+
+ if (BN_cmp(&expected, &value) != 0) {
+ printf("Unsupported signature version\n");
+ goto vsb_done;
+ }
+
+ BN_clear(&expected);
+ BN_clear(&value);
+
+ /* Confirm that the length of the image matches with the length in
+ the authenticated attributes */
+ length = htobe64(length);
+ BN_bin2bn((const unsigned char *) &length, sizeof(length), &expected);
+
+ ASN1_INTEGER_to_BN(bs->authenticatedAttributes->length, &value);
+
+ if (BN_cmp(&expected, &value) != 0) {
+ printf("Image length doesn't match signature attributes\n");
+ goto vsb_done;
+ }
+
+ rc = 0;
+
+vsb_done:
+ BN_free(&expected);
+ BN_free(&value);
+
+ return rc;
+}
+
+/**
+ * Creates a SHA-256 hash from the boot image contents and the encoded
+ * authenticated attributes.
+ * @param fd File descriptor to the boot image
+ * @param length Length of the boot image without the signature block
+ * @param aa Pointer to AuthAttrs
+ * @param digest Pointer to a buffer where the hash is written
+ */
+static int hash_image(int fd, uint64_t length, const AuthAttrs *aa,
+ unsigned char *digest)
+{
+ EVP_MD_CTX *ctx = NULL;
+ int rc = -1;
+
+ ssize_t bytes = 0;
+ unsigned char *attrs = NULL;
+ unsigned char *buffer = NULL;
+ unsigned char *p = NULL;
+ uint64_t total = 0;
+
+ if (!aa || !digest) {
+ goto hi_done;
+ }
+
+ if ((buffer = malloc(BUFFER_SIZE)) == NULL) {
+ goto hi_done;
+ }
+
+ if (lseek64(fd, 0, SEEK_SET) != 0) {
+ goto hi_done;
+ }
+
+ if ((ctx = EVP_MD_CTX_create()) == NULL) {
+ ERR_print_errors(g_error);
+ goto hi_done;
+ }
+
+ EVP_DigestInit(ctx, EVP_sha256());
+
+ do {
+ bytes = BUFFER_SIZE;
+
+ if ((length - total) < BUFFER_SIZE) {
+ bytes = length - total;
+ }
+
+ if ((bytes = read(fd, buffer, bytes)) == -1) {
+ printf("%s\n", strerror(errno));
+ goto hi_done;
+ }
+
+ EVP_DigestUpdate(ctx, buffer, bytes);
+ total += bytes;
+ } while (total < length);
+
+ if ((bytes = i2d_AuthAttrs((AuthAttrs *) aa, NULL)) < 0) {
+ ERR_print_errors(g_error);
+ goto hi_done;
+ }
+
+ if ((attrs = OPENSSL_malloc(bytes)) == NULL) {
+ ERR_print_errors(g_error);
+ goto hi_done;
+ }
+
+ p = attrs;
+
+ if (i2d_AuthAttrs((AuthAttrs *) aa, &p) < 0) {
+ ERR_print_errors(g_error);
+ goto hi_done;
+ }
+
+ EVP_DigestUpdate(ctx, attrs, bytes);
+ EVP_DigestFinal(ctx, digest, NULL);
+
+ rc = 0;
+
+hi_done:
+ if (buffer) {
+ free(buffer);
+ }
+
+ if (ctx) {
+ EVP_MD_CTX_destroy(ctx);
+ }
+
+ if (attrs) {
+ OPENSSL_free(attrs);
+ }
+
+ return rc;
+}
+
+/**
+ * Verifies the RSA signature
+ * @param fd File descriptor to the boot image
+ * @param length Length of the boot image without the signature block
+ * @param bs The boot signature block
+ */
+static int verify_signature(int fd, uint64_t length, const BootSignature *bs)
+{
+ int rc = -1;
+ EVP_PKEY *pkey = NULL;
+ RSA *rsa = NULL;
+ unsigned char digest[SHA256_DIGEST_LENGTH];
+
+ if (!bs) {
+ goto vs_done;
+ }
+
+ if (hash_image(fd, length, bs->authenticatedAttributes, digest) == -1) {
+ goto vs_done;
+ }
+
+ if ((pkey = X509_get_pubkey(bs->certificate)) == NULL) {
+ ERR_print_errors(g_error);
+ goto vs_done;
+ }
+
+ if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL) {
+ ERR_print_errors(g_error);
+ goto vs_done;
+ }
+
+ if (!RSA_verify(NID_sha256, digest, SHA256_DIGEST_LENGTH,
+ bs->signature->data, bs->signature->length, rsa)) {
+ ERR_print_errors(g_error);
+ goto vs_done;
+ }
+
+ rc = 0;
+
+vs_done:
+ if (pkey) {
+ EVP_PKEY_free(pkey);
+ }
+
+ if (rsa) {
+ RSA_free(rsa);
+ }
+
+ return rc;
+}
+
+/**
+ * Given the file name of a signed boot image, verifies the signature
+ * @param image_file Name of the boot image file
+ */
+static int verify(const char *image_file)
+{
+ BootSignature *bs = NULL;
+ int fd = -1;
+ int rc = 1;
+ off64_t offset = 0;
+
+ if (!image_file) {
+ return rc;
+ }
+
+ if ((fd = open(image_file, O_RDONLY | O_LARGEFILE)) == -1) {
+ return rc;
+ }
+
+ if (get_signature_offset(fd, &offset) == -1) {
+ goto out;
+ }
+
+ if (read_signature(fd, offset, &bs) == -1) {
+ goto out;
+ }
+
+ if (validate_signature_block(bs, offset) == -1) {
+ goto out;
+ }
+
+ if (verify_signature(fd, offset, bs) == -1) {
+ goto out;
+ }
+
+ printf("Signature is VALID\n");
+ rc = 0;
+
+out:
+ if (bs) {
+ BootSignature_free(bs);
+ }
+
+ if (fd != -1) {
+ close(fd);
+ }
+
+ return rc;
+}
+
+static void usage()
+{
+ printf("Usage: verify_boot_signature <path-to-boot-image>\n");
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc != 2) {
+ usage();
+ return 1;
+ }
+
+ /* BIO descriptor for logging OpenSSL errors to stderr */
+ if ((g_error = BIO_new_fd(STDERR_FILENO, BIO_NOCLOSE)) == NULL) {
+ printf("Failed to allocate a BIO handle for error output\n");
+ return 1;
+ }
+
+ ERR_load_crypto_strings();
+
+ return verify(argv[1]);
+}
diff --git a/verity/verity_verifier b/verity/verity_verifier
new file mode 100755
index 0000000..f145228
--- /dev/null
+++ b/verity/verity_verifier
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# Start-up script for VerityVerifier
+
+VERITYVERIFIER_HOME=`dirname "$0"`
+VERITYVERIFIER_HOME=`dirname "$VERITYVERIFIER_HOME"`
+
+java -Xmx512M -jar "$VERITYVERIFIER_HOME"/framework/VerityVerifier.jar "$@"