Project import
diff --git a/verity/Android.mk b/verity/Android.mk
new file mode 100644
index 0000000..1a7351f
--- /dev/null
+++ b/verity/Android.mk
@@ -0,0 +1,92 @@
+LOCAL_PATH:= $(call my-dir)
+
+ifeq ($(HOST_OS),linux)
+
+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
+LOCAL_C_INCLUDES += external/openssl/include system/extras/ext4_utils system/core/mkbootimg
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # HOST_OS == linux
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := generate_verity_key
+LOCAL_SRC_FILES := generate_verity_key.c
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+LOCAL_SHARED_LIBRARIES := libcrypto_utils libcrypto
+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
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := BootSignature.java VeritySigner.java Utils.java
+LOCAL_MODULE := BootSignature
+LOCAL_JAR_MANIFEST := BootSignature.mf
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host
+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
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_REQUIRED_MODULES := VeritySigner
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := boot_signer
+LOCAL_MODULE := boot_signer
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_REQUIRED_MODULES := BootSignature
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := build_verity_metadata.py
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_SRC_FILES := build_verity_metadata.py
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := build_verity_tree
+LOCAL_SRC_FILES := build_verity_tree.cpp
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_LIBRARIES := libsparse_host libz
+LOCAL_SHARED_LIBRARIES := libcrypto libbase
+LOCAL_CFLAGS += -Wall -Werror
+include $(BUILD_HOST_EXECUTABLE)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/verity/BootSignature.java b/verity/BootSignature.java
new file mode 100644
index 0000000..3cf9499
--- /dev/null
+++ b/verity/BootSignature.java
@@ -0,0 +1,319 @@
+/*
+ * 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.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.util.ASN1Dump;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+/**
+ * AndroidVerifiedBootSignature DEFINITIONS ::=
+ * BEGIN
+ * formatVersion ::= INTEGER
+ * certificate ::= Certificate
+ * algorithmIdentifier ::= SEQUENCE {
+ * algorithm OBJECT IDENTIFIER,
+ * parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ * authenticatedAttributes ::= SEQUENCE {
+ * target CHARACTER STRING,
+ * length INTEGER
+ * }
+ * 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(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 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();
+ 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 Utils.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 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);
+
+ 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);
+ }
+
+ public static void verifySignature(String imagePath, String certPath) 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);
+
+ if (!certPath.isEmpty()) {
+ System.err.println("NOTE: verifying using public key from " + certPath);
+ bootsig.setCertificate(Utils.loadPEMCertificate(certPath));
+ }
+
+ 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);
+ }
+
+ /* 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])) {
+ 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 */
+ verifySignature(args[1], certPath);
+ } 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/BootSignature.mf b/verity/BootSignature.mf
new file mode 100644
index 0000000..c1868b6
--- /dev/null
+++ b/verity/BootSignature.mf
@@ -0,0 +1 @@
+Main-Class: com.android.verity.BootSignature
diff --git a/verity/NOTICE b/verity/NOTICE
new file mode 100644
index 0000000..9109eed
--- /dev/null
+++ b/verity/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2013-2015, 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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/verity/Utils.java b/verity/Utils.java
new file mode 100644
index 0000000..6d80276
--- /dev/null
+++ b/verity/Utils.java
@@ -0,0 +1,299 @@
+/*
+ * 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.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.ECPublicKeySpec;
+import java.security.spec.ECPrivateKeySpec;
+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.asn1.x9.X9ObjectIdentifiers;
+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(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 void loadProviderIfNecessary(String providerClassName) {
+ if (providerClassName == null) {
+ return;
+ }
+
+ final Class<?> klass;
+ try {
+ final ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
+ if (sysLoader != null) {
+ klass = sysLoader.loadClass(providerClassName);
+ } else {
+ klass = Class.forName(providerClassName);
+ }
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ System.exit(1);
+ return;
+ }
+
+ Constructor<?> constructor = null;
+ for (Constructor<?> c : klass.getConstructors()) {
+ if (c.getParameterTypes().length == 0) {
+ constructor = c;
+ break;
+ }
+ }
+ if (constructor == null) {
+ System.err.println("No zero-arg constructor found for " + providerClassName);
+ System.exit(1);
+ return;
+ }
+
+ final Object o;
+ try {
+ o = constructor.newInstance();
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.exit(1);
+ return;
+ }
+ if (!(o instanceof Provider)) {
+ System.err.println("Not a Provider class: " + providerClassName);
+ System.exit(1);
+ }
+
+ Security.insertProviderAt((Provider) o, 1);
+ }
+
+ static byte[] pemToDer(String pem) throws Exception {
+ pem = pem.replaceAll("^-.*", "");
+ String base64_der = pem.replaceAll("-.*$", "");
+ 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 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 {
+ byte[] der = pemToDer(new String(pem));
+ return loadDERPrivateKey(der);
+ }
+
+ static PrivateKey loadPEMPrivateKeyFromFile(String keyFname) throws Exception {
+ return loadPEMPrivateKey(read(keyFname));
+ }
+
+ static PrivateKey loadDERPrivateKeyFromFile(String keyFname) throws Exception {
+ return loadDERPrivateKey(read(keyFname));
+ }
+
+ static PublicKey loadDERPublicKey(byte[] der) throws Exception {
+ X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(der);
+ KeyFactory factory = KeyFactory.getInstance("RSA");
+ return factory.generatePublic(publicKeySpec);
+ }
+
+ static PublicKey loadPEMPublicKey(byte[] pem) throws Exception {
+ byte[] der = pemToDer(new String(pem));
+ X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(der);
+ KeyFactory factory = KeyFactory.getInstance("RSA");
+ return factory.generatePublic(publicKeySpec);
+ }
+
+ static PublicKey loadPEMPublicKeyFromFile(String keyFname) throws Exception {
+ return loadPEMPublicKey(read(keyFname));
+ }
+
+ static PublicKey loadDERPublicKeyFromFile(String keyFname) throws Exception {
+ 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) 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 byte[] read(String fname) throws Exception {
+ long offset = 0;
+ File f = new File(fname);
+ long length = f.length();
+ byte[] image = new byte[(int)length];
+ FileInputStream fis = new FileInputStream(f);
+ while (offset < length) {
+ offset += fis.read(image, (int)offset, (int)(length - offset));
+ }
+ fis.close();
+ return image;
+ }
+
+ static void write(byte[] data, String fname) throws Exception{
+ FileOutputStream out = new FileOutputStream(fname);
+ out.write(data);
+ out.close();
+ }
+}
diff --git a/verity/VeritySigner.java b/verity/VeritySigner.java
new file mode 100644
index 0000000..9d85747
--- /dev/null
+++ b/verity/VeritySigner.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013 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.security.PublicKey;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class VeritySigner {
+
+ 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]);
+
+ 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/VeritySigner.mf b/verity/VeritySigner.mf
new file mode 100644
index 0000000..b36c198
--- /dev/null
+++ b/verity/VeritySigner.mf
@@ -0,0 +1 @@
+Main-Class: com.android.verity.VeritySigner
diff --git a/verity/VerityVerifier.java b/verity/VerityVerifier.java
new file mode 100644
index 0000000..6b3f49e
--- /dev/null
+++ b/verity/VerityVerifier.java
@@ -0,0 +1,419 @@
+/*
+ * 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.Math;
+import java.lang.Process;
+import java.lang.Runtime;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import javax.xml.bind.DatatypeConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class VerityVerifier {
+
+ private ArrayList<Integer> hashBlocksLevel;
+ private byte[] hashTree;
+ private byte[] rootHash;
+ private byte[] salt;
+ private byte[] signature;
+ private byte[] table;
+ private File image;
+ private int blockSize;
+ private int hashBlockSize;
+ private int hashOffsetForData;
+ private int hashSize;
+ private int hashTreeSize;
+ private long hashStart;
+ private long imageSize;
+ private MessageDigest digest;
+
+ 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 MINCRYPT_OFFSET_MODULUS = 0x8;
+ private static final int MINCRYPT_OFFSET_EXPONENT = 0x208;
+ private static final int MINCRYPT_MODULUS_SIZE = 0x100;
+ private static final int MINCRYPT_EXPONENT_SIZE = 0x4;
+ private static final int VERITY_FIELDS = 10;
+ private static final int VERITY_MAGIC = 0xB001B001;
+ private static final int VERITY_SIGNATURE_SIZE = 256;
+ private static final int VERITY_VERSION = 0;
+
+ public VerityVerifier(String fname) throws Exception {
+ digest = MessageDigest.getInstance("SHA-256");
+ hashSize = digest.getDigestLength();
+ hashBlocksLevel = new ArrayList<Integer>();
+ hashTreeSize = -1;
+ openImage(fname);
+ readVerityData();
+ }
+
+ /**
+ * Reverses the order of bytes in a byte array
+ * @param value Byte array to reverse
+ */
+ private static byte[] reverse(byte[] value) {
+ for (int i = 0; i < value.length / 2; i++) {
+ byte tmp = value[i];
+ value[i] = value[value.length - i - 1];
+ value[value.length - i - 1] = tmp;
+ }
+
+ return value;
+ }
+
+ /**
+ * Converts a 4-byte little endian value to a Java integer
+ * @param value Little endian integer to convert
+ */
+ private 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
+ */
+ private static int fromle(short value) {
+ return fromle(value << 16);
+ }
+
+ /**
+ * Reads a 2048-bit RSA public key saved in mincrypt format, and returns
+ * a Java PublicKey for it.
+ * @param fname Name of the mincrypt public key file
+ */
+ private static PublicKey getMincryptPublicKey(String fname) throws Exception {
+ try (RandomAccessFile key = new RandomAccessFile(fname, "r")) {
+ byte[] binaryMod = new byte[MINCRYPT_MODULUS_SIZE];
+ byte[] binaryExp = new byte[MINCRYPT_EXPONENT_SIZE];
+
+ key.seek(MINCRYPT_OFFSET_MODULUS);
+ key.readFully(binaryMod);
+
+ key.seek(MINCRYPT_OFFSET_EXPONENT);
+ key.readFully(binaryExp);
+
+ BigInteger modulus = new BigInteger(1, reverse(binaryMod));
+ BigInteger exponent = new BigInteger(1, reverse(binaryExp));
+
+ RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
+ KeyFactory factory = KeyFactory.getInstance("RSA");
+ return factory.generatePublic(spec);
+ }
+ }
+
+ /**
+ * Unsparses a sparse image into a temporary file and returns a
+ * handle to the file
+ * @param fname Path to a sparse image file
+ */
+ private void openImage(String fname) throws Exception {
+ image = File.createTempFile("system", ".raw");
+ image.deleteOnExit();
+
+ Process p = Runtime.getRuntime().exec("simg2img " + fname +
+ " " + image.getAbsoluteFile());
+
+ p.waitFor();
+ if (p.exitValue() != 0) {
+ throw new IllegalArgumentException("Invalid image: failed to unsparse");
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Calculates the size of the verity hash tree based on the image size
+ */
+ private int calculateHashTreeSize() {
+ if (hashTreeSize > 0) {
+ return hashTreeSize;
+ }
+
+ int totalBlocks = 0;
+ int hashes = (int) (imageSize / blockSize);
+
+ hashBlocksLevel.clear();
+
+ do {
+ hashBlocksLevel.add(0, hashes);
+
+ int hashBlocks =
+ (int) Math.ceil((double) hashes * hashSize / hashBlockSize);
+
+ totalBlocks += hashBlocks;
+
+ hashes = hashBlocks;
+ } while (hashes > 1);
+
+ hashTreeSize = totalBlocks * hashBlockSize;
+ return hashTreeSize;
+ }
+
+ /**
+ * Parses the verity mapping table and reads the hash tree from
+ * the image file
+ * @param img Handle to the image file
+ * @param table Verity mapping table
+ */
+ private void readHashTree(RandomAccessFile img, byte[] table)
+ throws Exception {
+ String tableStr = new String(table);
+ String[] fields = tableStr.split(" ");
+
+ if (fields.length != VERITY_FIELDS) {
+ throw new IllegalArgumentException("Invalid image: unexpected number of fields "
+ + "in verity mapping table (" + fields.length + ")");
+ }
+
+ String hashVersion = fields[0];
+
+ if (!"1".equals(hashVersion)) {
+ throw new IllegalArgumentException("Invalid image: unsupported hash format");
+ }
+
+ String alg = fields[7];
+
+ if (!"sha256".equals(alg)) {
+ throw new IllegalArgumentException("Invalid image: unsupported hash algorithm");
+ }
+
+ blockSize = Integer.parseInt(fields[3]);
+ hashBlockSize = Integer.parseInt(fields[4]);
+
+ int blocks = Integer.parseInt(fields[5]);
+ int start = Integer.parseInt(fields[6]);
+
+ if (imageSize != (long) blocks * blockSize) {
+ throw new IllegalArgumentException("Invalid image: size mismatch in mapping "
+ + "table");
+ }
+
+ rootHash = DatatypeConverter.parseHexBinary(fields[8]);
+ salt = DatatypeConverter.parseHexBinary(fields[9]);
+
+ hashStart = (long) start * blockSize;
+ img.seek(hashStart);
+
+ int treeSize = calculateHashTreeSize();
+
+ hashTree = new byte[treeSize];
+ img.readFully(hashTree);
+ }
+
+ /**
+ * Reads verity data from the image file
+ */
+ private void readVerityData() throws Exception {
+ try (RandomAccessFile img = new RandomAccessFile(image, "r")) {
+ imageSize = getMetadataPosition(img);
+ img.seek(imageSize);
+
+ 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");
+ }
+
+ signature = new byte[VERITY_SIGNATURE_SIZE];
+ img.readFully(signature);
+
+ int tableSize = fromle(img.readInt());
+
+ table = new byte[tableSize];
+ img.readFully(table);
+
+ readHashTree(img, table);
+ }
+ }
+
+ /**
+ * Reads and validates verity metadata, and checks the signature against the
+ * given public key
+ * @param key Public key to use for signature verification
+ */
+ public boolean verifyMetaData(PublicKey key)
+ throws Exception {
+ return Utils.verify(key, table, signature,
+ Utils.getSignatureAlgorithmIdentifier(key));
+ }
+
+ /**
+ * Hashes a block of data using a salt and checks of the results are expected
+ * @param hash The expected hash value
+ * @param data The data block to check
+ */
+ private boolean checkBlock(byte[] hash, byte[] data) {
+ digest.reset();
+ digest.update(salt);
+ digest.update(data);
+ return Arrays.equals(hash, digest.digest());
+ }
+
+ /**
+ * Verifies the root hash and the first N-1 levels of the hash tree
+ */
+ private boolean verifyHashTree() throws Exception {
+ int hashOffset = 0;
+ int dataOffset = hashBlockSize;
+
+ if (!checkBlock(rootHash, Arrays.copyOfRange(hashTree, 0, hashBlockSize))) {
+ System.err.println("Root hash mismatch");
+ return false;
+ }
+
+ for (int level = 0; level < hashBlocksLevel.size() - 1; level++) {
+ int blocks = hashBlocksLevel.get(level);
+
+ for (int i = 0; i < blocks; i++) {
+ byte[] hashBlock = Arrays.copyOfRange(hashTree,
+ hashOffset + i * hashSize,
+ hashOffset + i * hashSize + hashSize);
+
+ byte[] dataBlock = Arrays.copyOfRange(hashTree,
+ dataOffset + i * hashBlockSize,
+ dataOffset + i * hashBlockSize + hashBlockSize);
+
+ if (!checkBlock(hashBlock, dataBlock)) {
+ System.err.printf("Hash mismatch at tree level %d, block %d\n", level, i);
+ return false;
+ }
+ }
+
+ hashOffset = dataOffset;
+ hashOffsetForData = dataOffset;
+ dataOffset += blocks * hashBlockSize;
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates the image against the hash tree
+ */
+ public boolean verifyData() throws Exception {
+ if (!verifyHashTree()) {
+ return false;
+ }
+
+ try (RandomAccessFile img = new RandomAccessFile(image, "r")) {
+ byte[] dataBlock = new byte[blockSize];
+ int hashOffset = hashOffsetForData;
+
+ for (int i = 0; (long) i * blockSize < imageSize; i++) {
+ byte[] hashBlock = Arrays.copyOfRange(hashTree,
+ hashOffset + i * hashSize,
+ hashOffset + i * hashSize + hashSize);
+
+ img.readFully(dataBlock);
+
+ if (!checkBlock(hashBlock, dataBlock)) {
+ System.err.printf("Hash mismatch at block %d\n", i);
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Verifies the integrity of the image and the verity metadata
+ * @param key Public key to use for signature verification
+ */
+ public boolean verify(PublicKey key) throws Exception {
+ return (verifyMetaData(key) && verifyData());
+ }
+
+ public static void main(String[] args) throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+ PublicKey key = null;
+
+ if (args.length == 3 && "-mincrypt".equals(args[1])) {
+ key = getMincryptPublicKey(args[2]);
+ } else if (args.length == 2) {
+ X509Certificate cert = Utils.loadPEMCertificate(args[1]);
+ key = cert.getPublicKey();
+ } else {
+ System.err.println("Usage: VerityVerifier <sparse.img> <certificate.x509.pem> | -mincrypt <mincrypt_key>");
+ System.exit(1);
+ }
+
+ VerityVerifier verifier = new VerityVerifier(args[0]);
+
+ try {
+ if (verifier.verify(key)) {
+ System.err.println("Signature is VALID");
+ System.exit(0);
+ }
+ } 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/boot_signer b/verity/boot_signer
new file mode 100755
index 0000000..e2ee42b
--- /dev/null
+++ b/verity/boot_signer
@@ -0,0 +1,8 @@
+#! /bin/sh
+
+# Start-up script for BootSigner
+
+BOOTSIGNER_HOME=`dirname "$0"`
+BOOTSIGNER_HOME=`dirname "$BOOTSIGNER_HOME"`
+
+java -Xmx512M -jar "$BOOTSIGNER_HOME"/framework/BootSignature.jar "$@"
\ No newline at end of file
diff --git a/verity/build_verity_metadata.py b/verity/build_verity_metadata.py
new file mode 100755
index 0000000..51e629a
--- /dev/null
+++ b/verity/build_verity_metadata.py
@@ -0,0 +1,78 @@
+#! /usr/bin/env python
+
+import os
+import sys
+import struct
+import tempfile
+import commands
+
+VERSION = 0
+MAGIC_NUMBER = 0xb001b001
+BLOCK_SIZE = 4096
+METADATA_SIZE = BLOCK_SIZE * 8
+
+def run(cmd):
+ status, output = commands.getstatusoutput(cmd)
+ print output
+ if status:
+ exit(-1)
+
+def get_verity_metadata_size(data_size):
+ return METADATA_SIZE
+
+def build_metadata_block(verity_table, signature):
+ table_len = len(verity_table)
+ block = struct.pack("II256sI", MAGIC_NUMBER, VERSION, signature, table_len)
+ block += verity_table
+ block = block.ljust(METADATA_SIZE, '\x00')
+ return block
+
+def sign_verity_table(table, signer_path, key_path):
+ with tempfile.NamedTemporaryFile(suffix='.table') as table_file:
+ with tempfile.NamedTemporaryFile(suffix='.sig') as signature_file:
+ table_file.write(table)
+ table_file.flush()
+ cmd = " ".join((signer_path, table_file.name, key_path, signature_file.name))
+ print cmd
+ run(cmd)
+ return signature_file.read()
+
+def build_verity_table(block_device, data_blocks, root_hash, salt):
+ table = "1 %s %s %s %s %s %s sha256 %s %s"
+ table %= ( block_device,
+ block_device,
+ BLOCK_SIZE,
+ BLOCK_SIZE,
+ data_blocks,
+ data_blocks,
+ root_hash,
+ salt)
+ return table
+
+def build_verity_metadata(data_blocks, metadata_image, root_hash,
+ salt, block_device, signer_path, signing_key):
+ # build the verity table
+ verity_table = build_verity_table(block_device, data_blocks, root_hash, salt)
+ # build the verity table signature
+ signature = sign_verity_table(verity_table, signer_path, signing_key)
+ # build the metadata block
+ metadata_block = build_metadata_block(verity_table, signature)
+ # write it to the outfile
+ with open(metadata_image, "wb") as f:
+ f.write(metadata_block)
+
+if __name__ == "__main__":
+ if len(sys.argv) == 3 and sys.argv[1] == "-s":
+ print get_verity_metadata_size(int(sys.argv[2]))
+ elif len(sys.argv) == 8:
+ data_image_blocks = int(sys.argv[1]) / 4096
+ metadata_image = sys.argv[2]
+ root_hash = sys.argv[3]
+ salt = sys.argv[4]
+ block_device = sys.argv[5]
+ signer_path = sys.argv[6]
+ signing_key = sys.argv[7]
+ build_verity_metadata(data_image_blocks, metadata_image, root_hash,
+ salt, block_device, signer_path, signing_key)
+ else:
+ exit(-1)
diff --git a/verity/build_verity_tree.cpp b/verity/build_verity_tree.cpp
new file mode 100644
index 0000000..c50e449
--- /dev/null
+++ b/verity/build_verity_tree.cpp
@@ -0,0 +1,367 @@
+#include <openssl/bn.h>
+#include <openssl/evp.h>
+#include <sparse/sparse.h>
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+
+struct sparse_hash_ctx {
+ unsigned char *hashes;
+ const unsigned char *salt;
+ uint64_t salt_size;
+ uint64_t hash_size;
+ uint64_t block_size;
+ const unsigned char *zero_block_hash;
+ const EVP_MD *md;
+};
+
+#define div_round_up(x,y) (((x) + (y) - 1)/(y))
+
+#define round_up(x,y) (div_round_up(x,y)*(y))
+
+#define FATAL(x...) { \
+ fprintf(stderr, x); \
+ exit(1); \
+}
+
+size_t verity_tree_blocks(uint64_t data_size, size_t block_size, size_t hash_size,
+ int level)
+{
+ size_t level_blocks = div_round_up(data_size, block_size);
+ int hashes_per_block = div_round_up(block_size, hash_size);
+
+ do {
+ level_blocks = div_round_up(level_blocks, hashes_per_block);
+ } while (level--);
+
+ return level_blocks;
+}
+
+int hash_block(const EVP_MD *md,
+ const unsigned char *block, size_t len,
+ const unsigned char *salt, size_t salt_len,
+ unsigned char *out, size_t *out_size)
+{
+ EVP_MD_CTX *mdctx;
+ unsigned int s;
+ int ret = 1;
+
+ mdctx = EVP_MD_CTX_create();
+ assert(mdctx);
+ ret &= EVP_DigestInit_ex(mdctx, md, NULL);
+ ret &= EVP_DigestUpdate(mdctx, salt, salt_len);
+ ret &= EVP_DigestUpdate(mdctx, block, len);
+ ret &= EVP_DigestFinal_ex(mdctx, out, &s);
+ EVP_MD_CTX_destroy(mdctx);
+ assert(ret == 1);
+ if (out_size) {
+ *out_size = s;
+ }
+ return 0;
+}
+
+int hash_blocks(const EVP_MD *md,
+ const unsigned char *in, size_t in_size,
+ unsigned char *out, size_t *out_size,
+ const unsigned char *salt, size_t salt_size,
+ size_t block_size)
+{
+ size_t s;
+ *out_size = 0;
+ for (size_t i = 0; i < in_size; i += block_size) {
+ hash_block(md, in + i, block_size, salt, salt_size, out, &s);
+ out += s;
+ *out_size += s;
+ }
+
+ return 0;
+}
+
+int hash_chunk(void *priv, const void *data, int len)
+{
+ struct sparse_hash_ctx *ctx = (struct sparse_hash_ctx *)priv;
+ assert(len % ctx->block_size == 0);
+ if (data) {
+ size_t s;
+ hash_blocks(ctx->md, (const unsigned char *)data, len,
+ ctx->hashes, &s,
+ ctx->salt, ctx->salt_size, ctx->block_size);
+ ctx->hashes += s;
+ } else {
+ for (size_t i = 0; i < (size_t)len; i += ctx->block_size) {
+ memcpy(ctx->hashes, ctx->zero_block_hash, ctx->hash_size);
+ ctx->hashes += ctx->hash_size;
+ }
+ }
+ return 0;
+}
+
+void usage(void)
+{
+ printf("usage: build_verity_tree [ <options> ] -s <size> | <data> <verity>\n"
+ "options:\n"
+ " -a,--salt-str=<string> set salt to <string>\n"
+ " -A,--salt-hex=<hex digits> set salt to <hex digits>\n"
+ " -h show this help\n"
+ " -s,--verity-size=<data size> print the size of the verity tree\n"
+ " -v, enable verbose logging\n"
+ " -S treat <data image> as a sparse file\n"
+ );
+}
+
+int main(int argc, char **argv)
+{
+ char *data_filename;
+ char *verity_filename;
+ unsigned char *salt = NULL;
+ size_t salt_size = 0;
+ bool sparse = false;
+ size_t block_size = 4096;
+ uint64_t calculate_size = 0;
+ bool verbose = false;
+
+ while (1) {
+ const static struct option long_options[] = {
+ {"salt-str", required_argument, 0, 'a'},
+ {"salt-hex", required_argument, 0, 'A'},
+ {"help", no_argument, 0, 'h'},
+ {"sparse", no_argument, 0, 'S'},
+ {"verity-size", required_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {NULL, 0, 0, 0}
+ };
+ int c = getopt_long(argc, argv, "a:A:hSs:v", long_options, NULL);
+ if (c < 0) {
+ break;
+ }
+
+ switch (c) {
+ case 'a':
+ salt_size = strlen(optarg);
+ salt = new unsigned char[salt_size]();
+ if (salt == NULL) {
+ FATAL("failed to allocate memory for salt\n");
+ }
+ memcpy(salt, optarg, salt_size);
+ break;
+ case 'A': {
+ BIGNUM *bn = NULL;
+ if(!BN_hex2bn(&bn, optarg)) {
+ FATAL("failed to convert salt from hex\n");
+ }
+ salt_size = BN_num_bytes(bn);
+ salt = new unsigned char[salt_size]();
+ if (salt == NULL) {
+ FATAL("failed to allocate memory for salt\n");
+ }
+ if((size_t)BN_bn2bin(bn, salt) != salt_size) {
+ FATAL("failed to convert salt to bytes\n");
+ }
+ }
+ break;
+ case 'h':
+ usage();
+ return 1;
+ case 'S':
+ sparse = true;
+ break;
+ case 's': {
+ char* endptr;
+ errno = 0;
+ unsigned long long int inSize = strtoull(optarg, &endptr, 0);
+ if (optarg[0] == '\0' || *endptr != '\0' ||
+ (errno == ERANGE && inSize == ULLONG_MAX)) {
+ FATAL("invalid value of verity-size\n");
+ }
+ if (inSize > UINT64_MAX) {
+ FATAL("invalid value of verity-size\n");
+ }
+ calculate_size = (uint64_t)inSize;
+ }
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ case '?':
+ usage();
+ return 1;
+ default:
+ abort();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ const EVP_MD *md = EVP_sha256();
+ if (!md) {
+ FATAL("failed to get digest\n");
+ }
+
+ size_t hash_size = EVP_MD_size(md);
+ assert(hash_size * 2 < block_size);
+
+ if (!salt || !salt_size) {
+ salt_size = hash_size;
+ salt = new unsigned char[salt_size];
+ if (salt == NULL) {
+ FATAL("failed to allocate memory for salt\n");
+ }
+
+ int random_fd = open("/dev/urandom", O_RDONLY);
+ if (random_fd < 0) {
+ FATAL("failed to open /dev/urandom\n");
+ }
+
+ ssize_t ret = read(random_fd, salt, salt_size);
+ if (ret != (ssize_t)salt_size) {
+ FATAL("failed to read %zu bytes from /dev/urandom: %zd %d\n", salt_size, ret, errno);
+ }
+ close(random_fd);
+ }
+
+ if (calculate_size) {
+ if (argc != 0) {
+ usage();
+ return 1;
+ }
+ size_t verity_blocks = 0;
+ size_t level_blocks;
+ int levels = 0;
+ do {
+ level_blocks = verity_tree_blocks(calculate_size, block_size, hash_size, levels);
+ levels++;
+ verity_blocks += level_blocks;
+ } while (level_blocks > 1);
+
+ printf("%" PRIu64 "\n", (uint64_t)verity_blocks * block_size);
+ return 0;
+ }
+
+ if (argc != 2) {
+ usage();
+ return 1;
+ }
+
+ data_filename = argv[0];
+ verity_filename = argv[1];
+
+ int fd = open(data_filename, O_RDONLY);
+ if (fd < 0) {
+ FATAL("failed to open %s\n", data_filename);
+ }
+
+ struct sparse_file *file;
+ if (sparse) {
+ file = sparse_file_import(fd, false, false);
+ } else {
+ file = sparse_file_import_auto(fd, false, verbose);
+ }
+
+ if (!file) {
+ FATAL("failed to read file %s\n", data_filename);
+ }
+
+ int64_t len = sparse_file_len(file, false, false);
+ if (len % block_size != 0) {
+ FATAL("file size %" PRIu64 " is not a multiple of %zu bytes\n",
+ len, block_size);
+ }
+
+ int levels = 0;
+ size_t verity_blocks = 0;
+ size_t level_blocks;
+
+ do {
+ level_blocks = verity_tree_blocks(len, block_size, hash_size, levels);
+ levels++;
+ verity_blocks += level_blocks;
+ } while (level_blocks > 1);
+
+ unsigned char *verity_tree = new unsigned char[verity_blocks * block_size]();
+ unsigned char **verity_tree_levels = new unsigned char *[levels + 1]();
+ size_t *verity_tree_level_blocks = new size_t[levels]();
+ if (verity_tree == NULL || verity_tree_levels == NULL || verity_tree_level_blocks == NULL) {
+ FATAL("failed to allocate memory for verity tree\n");
+ }
+
+ unsigned char *ptr = verity_tree;
+ for (int i = levels - 1; i >= 0; i--) {
+ verity_tree_levels[i] = ptr;
+ verity_tree_level_blocks[i] = verity_tree_blocks(len, block_size, hash_size, i);
+ ptr += verity_tree_level_blocks[i] * block_size;
+ }
+ assert(ptr == verity_tree + verity_blocks * block_size);
+ assert(verity_tree_level_blocks[levels - 1] == 1);
+
+ unsigned char zero_block_hash[hash_size];
+ unsigned char zero_block[block_size];
+ memset(zero_block, 0, block_size);
+ hash_block(md, zero_block, block_size, salt, salt_size, zero_block_hash, NULL);
+
+ unsigned char root_hash[hash_size];
+ verity_tree_levels[levels] = root_hash;
+
+ struct sparse_hash_ctx ctx;
+ ctx.hashes = verity_tree_levels[0];
+ ctx.salt = salt;
+ ctx.salt_size = salt_size;
+ ctx.hash_size = hash_size;
+ ctx.block_size = block_size;
+ ctx.zero_block_hash = zero_block_hash;
+ ctx.md = md;
+
+ sparse_file_callback(file, false, false, hash_chunk, &ctx);
+
+ sparse_file_destroy(file);
+ close(fd);
+
+ for (int i = 0; i < levels; i++) {
+ size_t out_size;
+ hash_blocks(md,
+ verity_tree_levels[i], verity_tree_level_blocks[i] * block_size,
+ verity_tree_levels[i + 1], &out_size,
+ salt, salt_size, block_size);
+ if (i < levels - 1) {
+ assert(div_round_up(out_size, block_size) == verity_tree_level_blocks[i + 1]);
+ } else {
+ assert(out_size == hash_size);
+ }
+ }
+
+ for (size_t i = 0; i < hash_size; i++) {
+ printf("%02x", root_hash[i]);
+ }
+ printf(" ");
+ for (size_t i = 0; i < salt_size; i++) {
+ printf("%02x", salt[i]);
+ }
+ printf("\n");
+
+ fd = open(verity_filename, O_WRONLY|O_CREAT, 0666);
+ if (fd < 0) {
+ FATAL("failed to open output file '%s'\n", verity_filename);
+ }
+ if (!android::base::WriteFully(fd, verity_tree, verity_blocks * block_size)) {
+ FATAL("failed to write '%s'\n", verity_filename);
+ }
+ close(fd);
+
+ delete[] verity_tree_levels;
+ delete[] verity_tree_level_blocks;
+ delete[] verity_tree;
+ delete[] salt;
+}
diff --git a/verity/fec/Android.mk b/verity/fec/Android.mk
new file mode 100644
index 0000000..33ef8b5
--- /dev/null
+++ b/verity/fec/Android.mk
@@ -0,0 +1,43 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+ifeq ($(HOST_OS),linux)
+LOCAL_SANITIZE := integer
+endif
+LOCAL_MODULE := fec
+LOCAL_SRC_FILES := main.cpp image.cpp
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_LIBRARIES := \
+ libsparse_host \
+ libz \
+ libcrypto_utils \
+ libcrypto \
+ libfec_host \
+ libfec_rs_host \
+ libext4_utils_host \
+ libsquashfs_utils_host
+LOCAL_SHARED_LIBRARIES := libbase
+LOCAL_CFLAGS += -Wall -Werror -O3
+LOCAL_C_INCLUDES += external/fec
+include $(BUILD_HOST_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_SANITIZE := integer
+LOCAL_MODULE := fec
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_SRC_FILES := main.cpp image.cpp
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_LIBRARIES := \
+ libcrypto_utils \
+ libcrypto \
+ libfec \
+ libfec_rs \
+ libbase \
+ libext4_utils_static \
+ libsquashfs_utils \
+ libcutils
+LOCAL_CFLAGS += -Wall -Werror -O3 -DIMAGE_NO_SPARSE=1
+LOCAL_C_INCLUDES += external/fec
+include $(BUILD_EXECUTABLE)
diff --git a/verity/fec/image.cpp b/verity/fec/image.cpp
new file mode 100644
index 0000000..610a462
--- /dev/null
+++ b/verity/fec/image.cpp
@@ -0,0 +1,629 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#undef NDEBUG
+#define _LARGEFILE64_SOURCE
+
+extern "C" {
+ #include <fec.h>
+}
+
+#include <assert.h>
+#include <android-base/file.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <openssl/sha.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#ifndef IMAGE_NO_SPARSE
+#include <sparse/sparse.h>
+#endif
+#include "image.h"
+
+#if defined(__linux__)
+ #include <linux/fs.h>
+#elif defined(__APPLE__)
+ #include <sys/disk.h>
+ #define BLKGETSIZE64 DKIOCGETBLOCKCOUNT
+ #define O_LARGEFILE 0
+#endif
+
+void image_init(image *ctx)
+{
+ memset(ctx, 0, sizeof(*ctx));
+}
+
+static void mmap_image_free(image *ctx)
+{
+ if (ctx->input) {
+ munmap(ctx->input, (size_t)ctx->inp_size);
+ close(ctx->inp_fd);
+ }
+
+ if (ctx->fec_mmap_addr) {
+ munmap(ctx->fec_mmap_addr, FEC_BLOCKSIZE + ctx->fec_size);
+ close(ctx->fec_fd);
+ }
+
+ if (!ctx->inplace && ctx->output) {
+ delete[] ctx->output;
+ }
+}
+
+static void file_image_free(image *ctx)
+{
+ assert(ctx->input == ctx->output);
+
+ if (ctx->input) {
+ delete[] ctx->input;
+ }
+
+ if (ctx->fec) {
+ delete[] ctx->fec;
+ }
+}
+
+void image_free(image *ctx)
+{
+ if (ctx->mmap) {
+ mmap_image_free(ctx);
+ } else {
+ file_image_free(ctx);
+ }
+
+ image_init(ctx);
+}
+
+static uint64_t get_size(int fd)
+{
+ struct stat st;
+
+ if (fstat(fd, &st) == -1) {
+ FATAL("failed to fstat: %s\n", strerror(errno));
+ }
+
+ uint64_t size = 0;
+
+ if (S_ISBLK(st.st_mode)) {
+ if (ioctl(fd, BLKGETSIZE64, &size) == -1) {
+ FATAL("failed to ioctl(BLKGETSIZE64): %s\n", strerror(errno));
+ }
+ } else if (S_ISREG(st.st_mode)) {
+ size = st.st_size;
+ } else {
+ FATAL("unknown file mode: %d\n", (int)st.st_mode);
+ }
+
+ return size;
+}
+
+static void calculate_rounds(uint64_t size, image *ctx)
+{
+ if (!size) {
+ FATAL("empty file?\n");
+ } else if (size % FEC_BLOCKSIZE) {
+ FATAL("file size %" PRIu64 " is not a multiple of %u bytes\n",
+ size, FEC_BLOCKSIZE);
+ }
+
+ ctx->inp_size = size;
+ ctx->blocks = fec_div_round_up(ctx->inp_size, FEC_BLOCKSIZE);
+ ctx->rounds = fec_div_round_up(ctx->blocks, ctx->rs_n);
+}
+
+static void mmap_image_load(const std::vector<int>& fds, image *ctx,
+ bool output_needed)
+{
+ if (fds.size() != 1) {
+ FATAL("multiple input files not supported with mmap\n");
+ }
+
+ int fd = fds.front();
+
+ calculate_rounds(get_size(fd), ctx);
+
+ /* check that we can memory map the file; on 32-bit platforms we are
+ limited to encoding at most 4 GiB files */
+ if (ctx->inp_size > SIZE_MAX) {
+ FATAL("cannot mmap %" PRIu64 " bytes\n", ctx->inp_size);
+ }
+
+ if (ctx->verbose) {
+ INFO("memory mapping '%s' (size %" PRIu64 ")\n", ctx->fec_filename,
+ ctx->inp_size);
+ }
+
+ int flags = PROT_READ;
+
+ if (ctx->inplace) {
+ flags |= PROT_WRITE;
+ }
+
+ void *p = mmap(NULL, (size_t)ctx->inp_size, flags, MAP_SHARED, fd, 0);
+
+ if (p == MAP_FAILED) {
+ FATAL("failed to mmap '%s' (size %" PRIu64 "): %s\n",
+ ctx->fec_filename, ctx->inp_size, strerror(errno));
+ }
+
+ ctx->inp_fd = fd;
+ ctx->input = (uint8_t *)p;
+
+ if (ctx->inplace) {
+ ctx->output = ctx->input;
+ } else if (output_needed) {
+ if (ctx->verbose) {
+ INFO("allocating %" PRIu64 " bytes of memory\n", ctx->inp_size);
+ }
+
+ ctx->output = new uint8_t[ctx->inp_size];
+
+ if (!ctx->output) {
+ FATAL("failed to allocate memory\n");
+ }
+
+ memcpy(ctx->output, ctx->input, ctx->inp_size);
+ }
+
+ /* fd is closed in mmap_image_free */
+}
+
+#ifndef IMAGE_NO_SPARSE
+static int process_chunk(void *priv, const void *data, int len)
+{
+ image *ctx = (image *)priv;
+ assert(len % FEC_BLOCKSIZE == 0);
+
+ if (data) {
+ memcpy(&ctx->input[ctx->pos], data, len);
+ }
+
+ ctx->pos += len;
+ return 0;
+}
+#endif
+
+static void file_image_load(const std::vector<int>& fds, image *ctx)
+{
+ uint64_t size = 0;
+#ifndef IMAGE_NO_SPARSE
+ std::vector<struct sparse_file *> files;
+#endif
+
+ for (auto fd : fds) {
+ uint64_t len = 0;
+
+#ifdef IMAGE_NO_SPARSE
+ if (ctx->sparse) {
+ FATAL("sparse files not supported\n");
+ }
+
+ len = get_size(fd);
+#else
+ struct sparse_file *file;
+
+ if (ctx->sparse) {
+ file = sparse_file_import(fd, false, false);
+ } else {
+ file = sparse_file_import_auto(fd, false, ctx->verbose);
+ }
+
+ if (!file) {
+ FATAL("failed to read file %s\n", ctx->fec_filename);
+ }
+
+ len = sparse_file_len(file, false, false);
+ files.push_back(file);
+#endif /* IMAGE_NO_SPARSE */
+
+ size += len;
+ }
+
+ calculate_rounds(size, ctx);
+
+ if (ctx->verbose) {
+ INFO("allocating %" PRIu64 " bytes of memory\n", ctx->inp_size);
+ }
+
+ ctx->input = new uint8_t[ctx->inp_size];
+
+ if (!ctx->input) {
+ FATAL("failed to allocate memory\n");
+ }
+
+ memset(ctx->input, 0, ctx->inp_size);
+ ctx->output = ctx->input;
+ ctx->pos = 0;
+
+#ifdef IMAGE_NO_SPARSE
+ for (auto fd : fds) {
+ uint64_t len = get_size(fd);
+
+ if (!android::base::ReadFully(fd, &ctx->input[ctx->pos], len)) {
+ FATAL("failed to read: %s\n", strerror(errno));
+ }
+
+ ctx->pos += len;
+ close(fd);
+ }
+#else
+ for (auto file : files) {
+ sparse_file_callback(file, false, false, process_chunk, ctx);
+ sparse_file_destroy(file);
+ }
+
+ for (auto fd : fds) {
+ close(fd);
+ }
+#endif
+}
+
+bool image_load(const std::vector<std::string>& filenames, image *ctx,
+ bool output_needed)
+{
+ assert(ctx->roots > 0 && ctx->roots < FEC_RSM);
+ ctx->rs_n = FEC_RSM - ctx->roots;
+
+ int flags = O_RDONLY;
+
+ if (ctx->inplace) {
+ flags = O_RDWR;
+ }
+
+ std::vector<int> fds;
+
+ for (const auto& fn : filenames) {
+ int fd = TEMP_FAILURE_RETRY(open(fn.c_str(), flags | O_LARGEFILE));
+
+ if (fd < 0) {
+ FATAL("failed to open file '%s': %s\n", fn.c_str(), strerror(errno));
+ }
+
+ fds.push_back(fd);
+ }
+
+ if (ctx->mmap) {
+ mmap_image_load(fds, ctx, output_needed);
+ } else {
+ file_image_load(fds, ctx);
+ }
+
+ return true;
+}
+
+bool image_save(const std::string& filename, image *ctx)
+{
+ if (ctx->inplace && ctx->mmap) {
+ return true; /* nothing to do */
+ }
+
+ /* TODO: support saving as a sparse file */
+ int fd = TEMP_FAILURE_RETRY(open(filename.c_str(),
+ O_WRONLY | O_CREAT | O_TRUNC, 0666));
+
+ if (fd < 0) {
+ FATAL("failed to open file '%s: %s'\n", filename.c_str(),
+ strerror(errno));
+ }
+
+ if (!android::base::WriteFully(fd, ctx->output, ctx->inp_size)) {
+ FATAL("failed to write to output: %s\n", strerror(errno));
+ }
+
+ close(fd);
+ return true;
+}
+
+static void mmap_image_ecc_new(image *ctx)
+{
+ if (ctx->verbose) {
+ INFO("mmaping '%s' (size %u)\n", ctx->fec_filename, ctx->fec_size);
+ }
+
+ int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename,
+ O_RDWR | O_CREAT, 0666));
+
+ if (fd < 0) {
+ FATAL("failed to open file '%s': %s\n", ctx->fec_filename,
+ strerror(errno));
+ }
+
+ assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
+ size_t fec_size = FEC_BLOCKSIZE + ctx->fec_size;
+
+ if (ftruncate(fd, fec_size) == -1) {
+ FATAL("failed to ftruncate file '%s': %s\n", ctx->fec_filename,
+ strerror(errno));
+ }
+
+ if (ctx->verbose) {
+ INFO("memory mapping '%s' (size %zu)\n", ctx->fec_filename, fec_size);
+ }
+
+ void *p = mmap(NULL, fec_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+ if (p == MAP_FAILED) {
+ FATAL("failed to mmap '%s' (size %zu): %s\n", ctx->fec_filename,
+ fec_size, strerror(errno));
+ }
+
+ ctx->fec_fd = fd;
+ ctx->fec_mmap_addr = (uint8_t *)p;
+ ctx->fec = ctx->fec_mmap_addr;
+}
+
+static void file_image_ecc_new(image *ctx)
+{
+ if (ctx->verbose) {
+ INFO("allocating %u bytes of memory\n", ctx->fec_size);
+ }
+
+ ctx->fec = new uint8_t[ctx->fec_size];
+
+ if (!ctx->fec) {
+ FATAL("failed to allocate %u bytes\n", ctx->fec_size);
+ }
+}
+
+bool image_ecc_new(const std::string& filename, image *ctx)
+{
+ assert(ctx->rounds > 0); /* image_load should be called first */
+
+ ctx->fec_filename = filename.c_str();
+ ctx->fec_size = ctx->rounds * ctx->roots * FEC_BLOCKSIZE;
+
+ if (ctx->mmap) {
+ mmap_image_ecc_new(ctx);
+ } else {
+ file_image_ecc_new(ctx);
+ }
+
+ return true;
+}
+
+bool image_ecc_load(const std::string& filename, image *ctx)
+{
+ int fd = TEMP_FAILURE_RETRY(open(filename.c_str(), O_RDONLY));
+
+ if (fd < 0) {
+ FATAL("failed to open file '%s': %s\n", filename.c_str(),
+ strerror(errno));
+ }
+
+ if (lseek64(fd, -FEC_BLOCKSIZE, SEEK_END) < 0) {
+ FATAL("failed to seek to header in '%s': %s\n", filename.c_str(),
+ strerror(errno));
+ }
+
+ assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
+
+ uint8_t header[FEC_BLOCKSIZE];
+ fec_header *p = (fec_header *)header;
+
+ if (!android::base::ReadFully(fd, header, sizeof(header))) {
+ FATAL("failed to read %zd bytes from '%s': %s\n", sizeof(header),
+ filename.c_str(), strerror(errno));
+ }
+
+ if (p->magic != FEC_MAGIC) {
+ FATAL("invalid magic in '%s': %08x\n", filename.c_str(), p->magic);
+ }
+
+ if (p->version != FEC_VERSION) {
+ FATAL("unsupported version in '%s': %u\n", filename.c_str(),
+ p->version);
+ }
+
+ if (p->size != sizeof(fec_header)) {
+ FATAL("unexpected header size in '%s': %u\n", filename.c_str(),
+ p->size);
+ }
+
+ if (p->roots == 0 || p->roots >= FEC_RSM) {
+ FATAL("invalid roots in '%s': %u\n", filename.c_str(), p->roots);
+ }
+
+ if (p->fec_size % p->roots || p->fec_size % FEC_BLOCKSIZE) {
+ FATAL("invalid length in '%s': %u\n", filename.c_str(), p->fec_size);
+ }
+
+ ctx->roots = (int)p->roots;
+ ctx->rs_n = FEC_RSM - ctx->roots;
+
+ calculate_rounds(p->inp_size, ctx);
+
+ if (!image_ecc_new(filename, ctx)) {
+ FATAL("failed to allocate ecc\n");
+ }
+
+ if (p->fec_size != ctx->fec_size) {
+ FATAL("inconsistent header in '%s'\n", filename.c_str());
+ }
+
+ if (lseek64(fd, 0, SEEK_SET) < 0) {
+ FATAL("failed to rewind '%s': %s", filename.c_str(), strerror(errno));
+ }
+
+ if (!ctx->mmap && !android::base::ReadFully(fd, ctx->fec, ctx->fec_size)) {
+ FATAL("failed to read %u bytes from '%s': %s\n", ctx->fec_size,
+ filename.c_str(), strerror(errno));
+ }
+
+ close(fd);
+
+ uint8_t hash[SHA256_DIGEST_LENGTH];
+ SHA256(ctx->fec, ctx->fec_size, hash);
+
+ if (memcmp(hash, p->hash, SHA256_DIGEST_LENGTH) != 0) {
+ FATAL("invalid ecc data\n");
+ }
+
+ return true;
+}
+
+bool image_ecc_save(image *ctx)
+{
+ assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
+
+ uint8_t header[FEC_BLOCKSIZE];
+ uint8_t *p = header;
+
+ if (ctx->mmap) {
+ p = (uint8_t *)&ctx->fec_mmap_addr[ctx->fec_size];
+ }
+
+ memset(p, 0, FEC_BLOCKSIZE);
+
+ fec_header *f = (fec_header *)p;
+
+ f->magic = FEC_MAGIC;
+ f->version = FEC_VERSION;
+ f->size = sizeof(fec_header);
+ f->roots = ctx->roots;
+ f->fec_size = ctx->fec_size;
+ f->inp_size = ctx->inp_size;
+
+ SHA256(ctx->fec, ctx->fec_size, f->hash);
+
+ /* store a copy of the fec_header at the end of the header block */
+ memcpy(&p[sizeof(header) - sizeof(fec_header)], p, sizeof(fec_header));
+
+ if (!ctx->mmap) {
+ assert(ctx->fec_filename);
+
+ int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename,
+ O_WRONLY | O_CREAT | O_TRUNC, 0666));
+
+ if (fd < 0) {
+ FATAL("failed to open file '%s': %s\n", ctx->fec_filename,
+ strerror(errno));
+ }
+
+ if (!android::base::WriteFully(fd, ctx->fec, ctx->fec_size) ||
+ !android::base::WriteFully(fd, header, sizeof(header))) {
+ FATAL("failed to write to output: %s\n", strerror(errno));
+ }
+
+ close(fd);
+ }
+
+ return true;
+}
+
+static void * process(void *cookie)
+{
+ image_proc_ctx *ctx = (image_proc_ctx *)cookie;
+ ctx->func(ctx);
+ return NULL;
+}
+
+bool image_process(image_proc_func func, image *ctx)
+{
+ int threads = ctx->threads;
+
+ if (threads < IMAGE_MIN_THREADS) {
+ threads = sysconf(_SC_NPROCESSORS_ONLN);
+
+ if (threads < IMAGE_MIN_THREADS) {
+ threads = IMAGE_MIN_THREADS;
+ }
+ }
+
+ assert(ctx->rounds > 0);
+
+ if ((uint64_t)threads > ctx->rounds) {
+ threads = (int)ctx->rounds;
+ }
+ if (threads > IMAGE_MAX_THREADS) {
+ threads = IMAGE_MAX_THREADS;
+ }
+
+ if (ctx->verbose) {
+ INFO("starting %d threads to compute RS(255, %d)\n", threads,
+ ctx->rs_n);
+ }
+
+ pthread_t pthreads[threads];
+ image_proc_ctx args[threads];
+
+ uint64_t current = 0;
+ uint64_t end = ctx->rounds * ctx->rs_n * FEC_BLOCKSIZE;
+ uint64_t rs_blocks_per_thread =
+ fec_div_round_up(ctx->rounds * FEC_BLOCKSIZE, threads);
+
+ if (ctx->verbose) {
+ INFO("computing %" PRIu64 " codes per thread\n", rs_blocks_per_thread);
+ }
+
+ for (int i = 0; i < threads; ++i) {
+ args[i].func = func;
+ args[i].id = i;
+ args[i].ctx = ctx;
+ args[i].rv = 0;
+ args[i].fec_pos = current * ctx->roots;
+ args[i].start = current * ctx->rs_n;
+ args[i].end = (current + rs_blocks_per_thread) * ctx->rs_n;
+
+ args[i].rs = init_rs_char(FEC_PARAMS(ctx->roots));
+
+ if (!args[i].rs) {
+ FATAL("failed to initialize encoder for thread %d\n", i);
+ }
+
+ if (args[i].end > end) {
+ args[i].end = end;
+ } else if (i == threads && args[i].end + rs_blocks_per_thread *
+ ctx->rs_n > end) {
+ args[i].end = end;
+ }
+
+ if (ctx->verbose) {
+ INFO("thread %d: [%" PRIu64 ", %" PRIu64 ")\n",
+ i, args[i].start, args[i].end);
+ }
+
+ assert(args[i].start < args[i].end);
+ assert((args[i].end - args[i].start) % ctx->rs_n == 0);
+
+ if (pthread_create(&pthreads[i], NULL, process, &args[i]) != 0) {
+ FATAL("failed to create thread %d\n", i);
+ }
+
+ current += rs_blocks_per_thread;
+ }
+
+ ctx->rv = 0;
+
+ for (int i = 0; i < threads; ++i) {
+ if (pthread_join(pthreads[i], NULL) != 0) {
+ FATAL("failed to join thread %d: %s\n", i, strerror(errno));
+ }
+
+ ctx->rv += args[i].rv;
+
+ if (args[i].rs) {
+ free_rs_char(args[i].rs);
+ args[i].rs = NULL;
+ }
+ }
+
+ return true;
+}
diff --git a/verity/fec/image.h b/verity/fec/image.h
new file mode 100644
index 0000000..f0211fd
--- /dev/null
+++ b/verity/fec/image.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef __FEC_H__
+#define __FEC_H__
+
+#include <utils/Compat.h>
+#include <string>
+#include <vector>
+#include <fec/io.h>
+#include <fec/ecc.h>
+
+#define IMAGE_MIN_THREADS 1
+#define IMAGE_MAX_THREADS 128
+
+#define INFO(x...) \
+ fprintf(stderr, x);
+#define FATAL(x...) { \
+ fprintf(stderr, x); \
+ exit(1); \
+}
+
+#define unlikely(x) __builtin_expect(!!(x), 0)
+
+struct image {
+ /* if true, decode file in place instead of creating a new output file */
+ bool inplace;
+ /* if true, use memory mapping instead of copying all input into memory */
+ bool mmap;
+ /* if true, assume input is a sparse file */
+ bool sparse;
+ /* if true, print more verbose information to stderr */
+ bool verbose;
+ const char *fec_filename;
+ int fec_fd;
+ int inp_fd;
+ /* the number of Reed-Solomon generator polynomial roots, also the number
+ of parity bytes generated for each N bytes in RS(M, N) */
+ int roots;
+ /* for RS(M, N), N = M - roots */
+ int rs_n;
+ int threads;
+ uint32_t fec_size;
+ uint64_t blocks;
+ uint64_t inp_size;
+ uint64_t pos;
+ uint64_t rounds;
+ uint64_t rv;
+ uint8_t *fec;
+ uint8_t *fec_mmap_addr;
+ uint8_t *input;
+ uint8_t *output;
+};
+
+struct image_proc_ctx;
+typedef void (*image_proc_func)(image_proc_ctx *);
+
+struct image_proc_ctx {
+ image_proc_func func;
+ int id;
+ image *ctx;
+ uint64_t rv;
+ uint64_t fec_pos;
+ uint64_t start;
+ uint64_t end;
+ void *rs;
+};
+
+extern bool image_load(const std::vector<std::string>& filename, image *ctx,
+ bool output_needed);
+extern bool image_save(const std::string& filename, image *ctx);
+
+extern bool image_ecc_new(const std::string& filename, image *ctx);
+extern bool image_ecc_load(const std::string& filename, image *ctx);
+extern bool image_ecc_save(image *ctx);
+
+extern bool image_process(image_proc_func f, image *ctx);
+
+extern void image_init(image *ctx);
+extern void image_free(image *ctx);
+
+inline uint8_t image_get_interleaved_byte(uint64_t i, image *ctx)
+{
+ uint64_t offset = fec_ecc_interleave(i, ctx->rs_n, ctx->rounds);
+
+ if (unlikely(offset >= ctx->inp_size)) {
+ return 0;
+ }
+
+ return ctx->input[offset];
+}
+
+inline void image_set_interleaved_byte(uint64_t i, image *ctx,
+ uint8_t value)
+{
+ uint64_t offset = fec_ecc_interleave(i, ctx->rs_n, ctx->rounds);
+
+ if (unlikely(offset >= ctx->inp_size)) {
+ assert(value == 0);
+ } else if (ctx->output && ctx->output[offset] != value) {
+ ctx->output[offset] = value;
+ }
+}
+
+#endif // __FEC_H__
diff --git a/verity/fec/main.cpp b/verity/fec/main.cpp
new file mode 100644
index 0000000..0675fd6
--- /dev/null
+++ b/verity/fec/main.cpp
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+extern "C" {
+ #include <fec.h>
+}
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <android-base/file.h>
+#include "image.h"
+
+enum {
+ MODE_ENCODE,
+ MODE_DECODE,
+ MODE_PRINTSIZE,
+ MODE_GETECCSTART,
+ MODE_GETVERITYSTART
+};
+
+static void encode_rs(struct image_proc_ctx *ctx)
+{
+ struct image *fcx = ctx->ctx;
+ int j;
+ uint8_t data[fcx->rs_n];
+ uint64_t i;
+
+ for (i = ctx->start; i < ctx->end; i += fcx->rs_n) {
+ for (j = 0; j < fcx->rs_n; ++j) {
+ data[j] = image_get_interleaved_byte(i + j, fcx);
+ }
+
+ encode_rs_char(ctx->rs, data, &fcx->fec[ctx->fec_pos]);
+ ctx->fec_pos += fcx->roots;
+ }
+}
+
+static void decode_rs(struct image_proc_ctx *ctx)
+{
+ struct image *fcx = ctx->ctx;
+ int j, rv;
+ uint8_t data[fcx->rs_n + fcx->roots];
+ uint64_t i;
+
+ assert(sizeof(data) == FEC_RSM);
+
+ for (i = ctx->start; i < ctx->end; i += fcx->rs_n) {
+ for (j = 0; j < fcx->rs_n; ++j) {
+ data[j] = image_get_interleaved_byte(i + j, fcx);
+ }
+
+ memcpy(&data[fcx->rs_n], &fcx->fec[ctx->fec_pos], fcx->roots);
+ rv = decode_rs_char(ctx->rs, data, NULL, 0);
+
+ if (rv < 0) {
+ FATAL("failed to recover [%" PRIu64 ", %" PRIu64 ")\n",
+ i, i + fcx->rs_n);
+ } else if (rv > 0) {
+ /* copy corrected data to output */
+ for (j = 0; j < fcx->rs_n; ++j) {
+ image_set_interleaved_byte(i + j, fcx, data[j]);
+ }
+
+ ctx->rv += rv;
+ }
+
+ ctx->fec_pos += fcx->roots;
+ }
+}
+
+static int usage()
+{
+ printf("fec: a tool for encoding and decoding files using RS(255, N).\n"
+ "\n"
+ "usage: fec <mode> [ <options> ] [ <data> <fec> [ <output> ] ]\n"
+ "mode:\n"
+ " -e --encode encode (default)\n"
+ " -d --decode decode\n"
+ " -s, --print-fec-size=<data size> print FEC size\n"
+ " -E, --get-ecc-start=data print ECC offset in data\n"
+ " -V, --get-verity-start=data print verity offset\n"
+ "options:\n"
+ " -h show this help\n"
+ " -v enable verbose logging\n"
+ " -r, --roots=<bytes> number of parity bytes\n"
+ " -m, --mmap use memory mapping\n"
+ " -j, --threads=<threads> number of threads to use\n"
+ " -S treat data as a sparse file\n"
+ "decoding options:\n"
+ " -i, --inplace correct <data> in place\n"
+ );
+
+ return 1;
+}
+
+static uint64_t parse_arg(const char *arg, const char *name, uint64_t maxval)
+{
+ char* endptr;
+ errno = 0;
+
+ unsigned long long int value = strtoull(arg, &endptr, 0);
+
+ if (arg[0] == '\0' || *endptr != '\0' ||
+ (errno == ERANGE && value == ULLONG_MAX)) {
+ FATAL("invalid value of %s\n", name);
+ }
+ if (value > maxval) {
+ FATAL("value of roots too large (max. %" PRIu64 ")\n", maxval);
+ }
+
+ return (uint64_t)value;
+}
+
+static int print_size(image& ctx)
+{
+ /* output size including header */
+ printf("%" PRIu64 "\n", fec_ecc_get_size(ctx.inp_size, ctx.roots));
+ return 0;
+}
+
+static int get_start(int mode, const std::string& filename)
+{
+ fec::io fh(filename, O_RDONLY, FEC_VERITY_DISABLE);
+
+ if (!fh) {
+ FATAL("failed to open input\n");
+ }
+
+ if (mode == MODE_GETECCSTART) {
+ fec_ecc_metadata data;
+
+ if (!fh.get_ecc_metadata(data)) {
+ FATAL("no ecc data\n");
+ }
+
+ printf("%" PRIu64 "\n", data.start);
+ } else {
+ fec_verity_metadata data;
+
+ if (!fh.get_verity_metadata(data)) {
+ FATAL("no verity data\n");
+ }
+
+ printf("%" PRIu64 "\n", data.data_size);
+ }
+
+ return 0;
+}
+
+static int encode(image& ctx, const std::vector<std::string>& inp_filenames,
+ const std::string& fec_filename)
+{
+ if (ctx.inplace) {
+ FATAL("invalid parameters: inplace can only used when decoding\n");
+ }
+
+ if (!image_load(inp_filenames, &ctx, false)) {
+ FATAL("failed to read input\n");
+ }
+
+ if (!image_ecc_new(fec_filename, &ctx)) {
+ FATAL("failed to allocate ecc\n");
+ }
+
+ INFO("encoding RS(255, %d) to '%s' for input files:\n", ctx.rs_n,
+ fec_filename.c_str());
+
+ size_t n = 1;
+
+ for (const auto& fn : inp_filenames) {
+ INFO("\t%zu: '%s'\n", n++, fn.c_str());
+ }
+
+ if (ctx.verbose) {
+ INFO("\traw fec size: %u\n", ctx.fec_size);
+ INFO("\tblocks: %" PRIu64 "\n", ctx.blocks);
+ INFO("\trounds: %" PRIu64 "\n", ctx.rounds);
+ }
+
+ if (!image_process(encode_rs, &ctx)) {
+ FATAL("failed to process input\n");
+ }
+
+ if (!image_ecc_save(&ctx)) {
+ FATAL("failed to write output\n");
+ }
+
+ image_free(&ctx);
+ return 0;
+}
+
+static int decode(image& ctx, const std::vector<std::string>& inp_filenames,
+ const std::string& fec_filename, std::string& out_filename)
+{
+ const std::string& inp_filename = inp_filenames.front();
+
+ if (ctx.inplace && ctx.sparse) {
+ FATAL("invalid parameters: inplace cannot be used with sparse "
+ "files\n");
+ }
+
+ if (!image_ecc_load(fec_filename, &ctx) ||
+ !image_load(inp_filenames, &ctx, !out_filename.empty())) {
+ FATAL("failed to read input\n");
+ }
+
+ if (ctx.inplace) {
+ INFO("correcting '%s' using RS(255, %d) from '%s'\n",
+ inp_filename.c_str(), ctx.rs_n, fec_filename.c_str());
+
+ out_filename = inp_filename;
+ } else {
+ INFO("decoding '%s' to '%s' using RS(255, %d) from '%s'\n",
+ inp_filename.c_str(),
+ out_filename.empty() ? out_filename.c_str() : "<none>", ctx.rs_n,
+ fec_filename.c_str());
+ }
+
+ if (ctx.verbose) {
+ INFO("\traw fec size: %u\n", ctx.fec_size);
+ INFO("\tblocks: %" PRIu64 "\n", ctx.blocks);
+ INFO("\trounds: %" PRIu64 "\n", ctx.rounds);
+ }
+
+ if (!image_process(decode_rs, &ctx)) {
+ FATAL("failed to process input\n");
+ }
+
+ if (ctx.rv) {
+ INFO("corrected %" PRIu64 " errors\n", ctx.rv);
+ } else {
+ INFO("no errors found\n");
+ }
+
+ if (!out_filename.empty() && !image_save(out_filename, &ctx)) {
+ FATAL("failed to write output\n");
+ }
+
+ image_free(&ctx);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ std::string fec_filename;
+ std::string out_filename;
+ std::vector<std::string> inp_filenames;
+ int mode = MODE_ENCODE;
+ image ctx;
+
+ image_init(&ctx);
+ ctx.roots = FEC_DEFAULT_ROOTS;
+
+ while (1) {
+ const static struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"encode", no_argument, 0, 'e'},
+ {"decode", no_argument, 0, 'd'},
+ {"sparse", no_argument, 0, 'S'},
+ {"roots", required_argument, 0, 'r'},
+ {"inplace", no_argument, 0, 'i'},
+ {"mmap", no_argument, 0, 'm'},
+ {"threads", required_argument, 0, 'j'},
+ {"print-fec-size", required_argument, 0, 's'},
+ {"get-ecc-start", required_argument, 0, 'E'},
+ {"get-verity-start", required_argument, 0, 'V'},
+ {"verbose", no_argument, 0, 'v'},
+ {NULL, 0, 0, 0}
+ };
+ int c = getopt_long(argc, argv, "hedSr:imj:s:E:V:v", long_options, NULL);
+ if (c < 0) {
+ break;
+ }
+
+ switch (c) {
+ case 'h':
+ return usage();
+ case 'S':
+ ctx.sparse = true;
+ break;
+ case 'e':
+ if (mode != MODE_ENCODE) {
+ return usage();
+ }
+ break;
+ case 'd':
+ if (mode != MODE_ENCODE) {
+ return usage();
+ }
+ mode = MODE_DECODE;
+ break;
+ case 'r':
+ ctx.roots = (int)parse_arg(optarg, "roots", FEC_RSM);
+ break;
+ case 'i':
+ ctx.inplace = true;
+ break;
+ case 'm':
+ ctx.mmap = true;
+ break;
+ case 'j':
+ ctx.threads = (int)parse_arg(optarg, "threads", IMAGE_MAX_THREADS);
+ break;
+ case 's':
+ if (mode != MODE_ENCODE) {
+ return usage();
+ }
+ mode = MODE_PRINTSIZE;
+ ctx.inp_size = parse_arg(optarg, "print-fec-size", UINT64_MAX);
+ break;
+ case 'E':
+ if (mode != MODE_ENCODE) {
+ return usage();
+ }
+ mode = MODE_GETECCSTART;
+ inp_filenames.push_back(optarg);
+ break;
+ case 'V':
+ if (mode != MODE_ENCODE) {
+ return usage();
+ }
+ mode = MODE_GETVERITYSTART;
+ inp_filenames.push_back(optarg);
+ break;
+ case 'v':
+ ctx.verbose = true;
+ break;
+ case '?':
+ return usage();
+ default:
+ abort();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ assert(ctx.roots > 0 && ctx.roots < FEC_RSM);
+
+ /* check for input / output parameters */
+ if (mode == MODE_ENCODE) {
+ /* allow multiple input files */
+ for (int i = 0; i < (argc - 1); ++i) {
+ inp_filenames.push_back(argv[i]);
+ }
+
+ if (inp_filenames.empty()) {
+ return usage();
+ }
+
+ /* the last one is the output file */
+ fec_filename = argv[argc - 1];
+ } else if (mode == MODE_DECODE) {
+ if (argc < 2 || argc > 3) {
+ return usage();
+ } else if (argc == 3) {
+ if (ctx.inplace) {
+ return usage();
+ }
+ out_filename = argv[2];
+ }
+
+ inp_filenames.push_back(argv[0]);
+ fec_filename = argv[1];
+ }
+
+ switch (mode) {
+ case MODE_PRINTSIZE:
+ return print_size(ctx);
+ case MODE_GETECCSTART:
+ case MODE_GETVERITYSTART:
+ return get_start(mode, inp_filenames.front());
+ case MODE_ENCODE:
+ return encode(ctx, inp_filenames, fec_filename);
+ case MODE_DECODE:
+ return decode(ctx, inp_filenames, fec_filename, out_filename);
+ default:
+ abort();
+ }
+
+ return 1;
+}
diff --git a/verity/fec/tests/fec.py b/verity/fec/tests/fec.py
new file mode 100644
index 0000000..71a1834
--- /dev/null
+++ b/verity/fec/tests/fec.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2015 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.
+
+import math
+import os
+import random
+import shutil
+import subprocess
+import sys
+import tempfile
+
+blocksize = 4096
+roots = 2
+
+def corrupt(image, offset, length):
+ print "corrupting %d bytes at offset %d" % (length, offset)
+ f = os.open(image, os.O_WRONLY)
+ os.lseek(f, offset, os.SEEK_SET)
+ os.write(f, os.urandom(length))
+ os.close(f)
+
+def corruptmax(image, roots):
+ size = os.stat(image).st_size
+
+ blocks = int(math.ceil(float(size) / blocksize))
+ rounds = int(math.ceil(float(blocks) / (255 - roots)))
+
+ max_errors = int(math.floor(rounds * roots / 2)) * blocksize
+ offset = random.randrange(0, size - max_errors)
+
+ corrupt(image, offset, max_errors)
+
+def encode(image, fec, roots):
+ if subprocess.call([ "fec", "--roots= " + str(roots), image, fec ]) != 0:
+ raise Exception("encoding failed")
+
+def decode(image, fec, output):
+ return subprocess.call([ "fec", "--decode", image, fec, output ])
+
+def compare(a, b):
+ return subprocess.call([ "cmp", "-s", a, b ])
+
+def simg2img(image, output):
+ print "creating a non-sparse copy of '%s' to '%s'" % (image, output)
+ if subprocess.call([ "simg2img", image, output]) != 0:
+ raise Exception("simg2img failed")
+
+def main(argv):
+ image = argv[0]
+
+ temp_img = tempfile.NamedTemporaryFile()
+ temp_cor = tempfile.NamedTemporaryFile()
+ temp_fec = tempfile.NamedTemporaryFile()
+ temp_out = tempfile.NamedTemporaryFile()
+
+ simg2img(image, temp_img.name)
+ simg2img(image, temp_cor.name)
+
+ encode(image, temp_fec.name, roots)
+ corruptmax(temp_cor.name, roots)
+
+ if decode(temp_cor.name, temp_fec.name, temp_out.name) != 0:
+ raise Exception("FAILED: failed to correct maximum expected errors")
+
+ if compare(temp_img.name, temp_out.name) != 0:
+ raise Exception("FAILED: corrected file not identical")
+ else:
+ print "corrected content matches original"
+
+ corrupt(temp_cor.name, 0, blocksize)
+
+ if decode(temp_cor.name, temp_fec.name, temp_out.name) == 0:
+ raise Exception("FAILED: corrected more than maximum number of errors?")
+
+ print "PASSED"
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/verity/generate_verity_key.c b/verity/generate_verity_key.c
new file mode 100644
index 0000000..c598afb
--- /dev/null
+++ b/verity/generate_verity_key.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2013 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 _GNU_SOURCE /* needed for asprintf */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <crypto_utils/android_pubkey.h>
+
+#include <openssl/evp.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/sha.h>
+
+static int write_public_keyfile(RSA *private_key, const char *private_key_path)
+{
+ uint8_t key_data[ANDROID_PUBKEY_ENCODED_SIZE];
+ BIO *bfile = NULL;
+ char *path = NULL;
+ int ret = -1;
+
+ if (asprintf(&path, "%s.pub", private_key_path) < 0)
+ goto out;
+
+ if (!android_pubkey_encode(private_key, key_data, sizeof(key_data)))
+ goto out;
+
+ bfile = BIO_new_file(path, "w");
+ if (!bfile)
+ goto out;
+
+ BIO_write(bfile, key_data, sizeof(key_data));
+ BIO_flush(bfile);
+
+ ret = 0;
+out:
+ BIO_free_all(bfile);
+ free(path);
+ 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;
+ FILE *f = NULL;
+ RSA* rsa = RSA_new();
+ BIGNUM* exponent = BN_new();
+ EVP_PKEY* pkey = EVP_PKEY_new();
+
+ if (!pkey || !exponent || !rsa) {
+ printf("Failed to allocate key\n");
+ goto out;
+ }
+
+ BN_set_word(exponent, RSA_F4);
+ RSA_generate_key_ex(rsa, 2048, exponent, NULL);
+ EVP_PKEY_set1_RSA(pkey, rsa);
+
+ f = fopen(file, "w");
+ if (!f) {
+ printf("Failed to open '%s'\n", file);
+ goto out;
+ }
+
+ if (!PEM_write_PrivateKey(f, pkey, NULL, NULL, 0, NULL, NULL)) {
+ printf("Failed to write key\n");
+ goto out;
+ }
+
+ if (write_public_keyfile(rsa, file) < 0) {
+ printf("Failed to write public key\n");
+ goto out;
+ }
+
+ ret = 0;
+
+out:
+ if (f)
+ fclose(f);
+ EVP_PKEY_free(pkey);
+ RSA_free(rsa);
+ BN_free(exponent);
+ return ret;
+}
+
+static void usage(){
+ 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) {
+ return generate_key(argv[1]);
+ } else if (argc == 4 && !strcmp(argv[1], "-convert")) {
+ return convert_x509(argv[2], argv[3]);
+ } else {
+ usage();
+ exit(-1);
+ }
+}
diff --git a/verity/verify_boot_signature.c b/verity/verify_boot_signature.c
new file mode 100644
index 0000000..b706e3a
--- /dev/null
+++ b/verity/verify_boot_signature.c
@@ -0,0 +1,429 @@
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+#include <openssl/crypto.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_signer b/verity/verity_signer
new file mode 100755
index 0000000..a4f337a
--- /dev/null
+++ b/verity/verity_signer
@@ -0,0 +1,8 @@
+#! /bin/sh
+
+# Start-up script for VeritySigner
+
+VERITYSIGNER_HOME=`dirname "$0"`
+VERITYSIGNER_HOME=`dirname "$VERITYSIGNER_HOME"`
+
+java -Xmx512M -jar "$VERITYSIGNER_HOME"/framework/VeritySigner.jar "$@"
\ No newline at end of file
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 "$@"