am ffc011b6: Merge "Implement simpleperf stat subcommand."
* commit 'ffc011b651535c46225630f5b73a798d86c1f52e':
Implement simpleperf stat subcommand.
diff --git a/ext4_utils/Android.mk b/ext4_utils/Android.mk
index 27b00bf..0808886 100644
--- a/ext4_utils/Android.mk
+++ b/ext4_utils/Android.mk
@@ -10,7 +10,6 @@
contents.c \
extent.c \
indirect.c \
- uuid.c \
sha1.c \
wipe.c \
crc16.c \
@@ -66,9 +65,11 @@
LOCAL_C_INCLUDES += system/core/logwrapper/include
LOCAL_SHARED_LIBRARIES := \
libcutils \
+ libext2_uuid \
libselinux \
libsparse \
libz
+LOCAL_CFLAGS := -DREAL_UUID
include $(BUILD_SHARED_LIBRARY)
@@ -86,9 +87,11 @@
LOCAL_MODULE := make_ext4fs
LOCAL_SHARED_LIBRARIES := \
libcutils \
+ libext2_uuid \
libext4_utils \
libselinux \
libz
+LOCAL_CFLAGS := -DREAL_UUID
include $(BUILD_EXECUTABLE)
diff --git a/ext4_utils/ext4_utils.c b/ext4_utils/ext4_utils.c
index ad0491f..3b22b81 100644
--- a/ext4_utils/ext4_utils.c
+++ b/ext4_utils/ext4_utils.c
@@ -15,12 +15,15 @@
*/
#include "ext4_utils.h"
-#include "uuid.h"
#include "allocate.h"
#include "indirect.h"
#include "extent.h"
+#include "sha1.h"
#include <sparse/sparse.h>
+#ifdef REAL_UUID
+#include <uuid.h>
+#endif
#include <fcntl.h>
#include <inttypes.h>
@@ -49,6 +52,44 @@
jmp_buf setjmp_env;
+/* Definition from RFC-4122 */
+struct uuid {
+ u32 time_low;
+ u16 time_mid;
+ u16 time_hi_and_version;
+ u8 clk_seq_hi_res;
+ u8 clk_seq_low;
+ u16 node0_1;
+ u32 node2_5;
+};
+
+static void sha1_hash(const char *namespace, const char *name,
+ unsigned char sha1[SHA1_DIGEST_LENGTH])
+{
+ SHA1_CTX ctx;
+ SHA1Init(&ctx);
+ SHA1Update(&ctx, (const u8*)namespace, strlen(namespace));
+ SHA1Update(&ctx, (const u8*)name, strlen(name));
+ SHA1Final(sha1, &ctx);
+}
+
+static void generate_sha1_uuid(const char *namespace, const char *name, u8 result[16])
+{
+ unsigned char sha1[SHA1_DIGEST_LENGTH];
+ struct uuid *uuid = (struct uuid *)result;
+
+ sha1_hash(namespace, name, (unsigned char*)sha1);
+ memcpy(uuid, sha1, sizeof(struct uuid));
+
+ uuid->time_low = ntohl(uuid->time_low);
+ uuid->time_mid = ntohs(uuid->time_mid);
+ uuid->time_hi_and_version = ntohs(uuid->time_hi_and_version);
+ uuid->time_hi_and_version &= 0x0FFF;
+ uuid->time_hi_and_version |= (5 << 12);
+ uuid->clk_seq_hi_res &= ~(1 << 6);
+ uuid->clk_seq_hi_res |= 1 << 7;
+}
+
/* returns 1 if a is a power of b */
static int is_power_of(int a, int b)
{
@@ -188,7 +229,7 @@
}
/* Fill in the superblock memory buffer based on the filesystem parameters */
-void ext4_fill_in_sb()
+void ext4_fill_in_sb(int real_uuid)
{
unsigned int i;
struct ext4_super_block *sb = aux_info.sb;
@@ -225,7 +266,16 @@
sb->s_feature_compat = info.feat_compat;
sb->s_feature_incompat = info.feat_incompat;
sb->s_feature_ro_compat = info.feat_ro_compat;
- generate_uuid("extandroid/make_ext4fs", info.label, sb->s_uuid);
+ if (real_uuid == 1) {
+#ifdef REAL_UUID
+ uuid_generate(sb->s_uuid);
+#else
+ fprintf(stderr, "Not compiled with real UUID support\n");
+ abort();
+#endif
+ } else {
+ generate_sha1_uuid("extandroid/make_ext4fs", info.label, sb->s_uuid);
+ }
memset(sb->s_volume_name, 0, sizeof(sb->s_volume_name));
strncpy(sb->s_volume_name, info.label, sizeof(sb->s_volume_name));
memset(sb->s_last_mounted, 0, sizeof(sb->s_last_mounted));
diff --git a/ext4_utils/ext4_utils.h b/ext4_utils/ext4_utils.h
index 499753f..ea95446 100644
--- a/ext4_utils/ext4_utils.h
+++ b/ext4_utils/ext4_utils.h
@@ -138,7 +138,7 @@
void write_ext4_image(int fd, int gz, int sparse, int crc);
void ext4_create_fs_aux_info(void);
void ext4_free_fs_aux_info(void);
-void ext4_fill_in_sb(void);
+void ext4_fill_in_sb(int real_uuid);
void ext4_create_resize_inode(void);
void ext4_create_journal_inode(void);
void ext4_update_free(void);
@@ -157,7 +157,7 @@
int make_ext4fs_internal(int fd, const char *directory,
const char *mountpoint, fs_config_func_t fs_config_func, int gzip,
- int sparse, int crc, int wipe,
+ int sparse, int crc, int wipe, int real_uuid,
struct selabel_handle *sehnd, int verbose, time_t fixed_time,
FILE* block_list_file);
diff --git a/ext4_utils/make_ext4fs.c b/ext4_utils/make_ext4fs.c
index 62a3f1a..5c9e208 100644
--- a/ext4_utils/make_ext4fs.c
+++ b/ext4_utils/make_ext4fs.c
@@ -18,7 +18,6 @@
#include "ext4_utils.h"
#include "allocate.h"
#include "contents.h"
-#include "uuid.h"
#include "wipe.h"
#include <sparse/sparse.h>
@@ -406,7 +405,7 @@
reset_ext4fs_info();
info.len = len;
- return make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 1, 0, 0, sehnd, 0, -1, NULL);
+ return make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 1, 0, 0, 0, sehnd, 0, -1, NULL);
}
int make_ext4fs(const char *filename, long long len,
@@ -424,7 +423,7 @@
return EXIT_FAILURE;
}
- status = make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 0, 0, 1, sehnd, 0, -1, NULL);
+ status = make_ext4fs_internal(fd, NULL, mountpoint, NULL, 0, 0, 0, 1, 0, sehnd, 0, -1, NULL);
close(fd);
return status;
@@ -492,7 +491,7 @@
int make_ext4fs_internal(int fd, const char *_directory,
const char *_mountpoint, fs_config_func_t fs_config_func, int gzip,
- int sparse, int crc, int wipe,
+ int sparse, int crc, int wipe, int real_uuid,
struct selabel_handle *sehnd, int verbose, time_t fixed_time,
FILE* block_list_file)
{
@@ -585,7 +584,7 @@
block_allocator_init();
- ext4_fill_in_sb();
+ ext4_fill_in_sb(real_uuid);
if (reserve_inodes(0, 10) == EXT4_ALLOCATE_FAILED)
error("failed to reserve first 10 inodes");
diff --git a/ext4_utils/make_ext4fs_main.c b/ext4_utils/make_ext4fs_main.c
index a6c5f61..0e2ef5e 100644
--- a/ext4_utils/make_ext4fs_main.c
+++ b/ext4_utils/make_ext4fs_main.c
@@ -52,7 +52,7 @@
{
fprintf(stderr, "%s [ -l <len> ] [ -j <journal size> ] [ -b <block_size> ]\n", basename(path));
fprintf(stderr, " [ -g <blocks per group> ] [ -i <inodes> ] [ -I <inode size> ]\n");
- fprintf(stderr, " [ -L <label> ] [ -f ] [ -a <android mountpoint> ]\n");
+ fprintf(stderr, " [ -L <label> ] [ -f ] [ -a <android mountpoint> ] [ -u ]\n");
fprintf(stderr, " [ -S file_contexts ] [ -C fs_config ] [ -T timestamp ]\n");
fprintf(stderr, " [ -z | -s ] [ -w ] [ -c ] [ -J ] [ -v ] [ -B <block_list_file> ]\n");
fprintf(stderr, " <filename> [<directory>]\n");
@@ -70,6 +70,7 @@
int sparse = 0;
int crc = 0;
int wipe = 0;
+ int real_uuid = 0;
int fd;
int exitcode;
int verbose = 0;
@@ -80,7 +81,7 @@
struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, "" } };
#endif
- while ((opt = getopt(argc, argv, "l:j:b:g:i:I:L:a:S:T:C:B:fwzJsctv")) != -1) {
+ while ((opt = getopt(argc, argv, "l:j:b:g:i:I:L:a:S:T:C:B:fwzJsctvu")) != -1) {
switch (opt) {
case 'l':
info.len = parse_num(optarg);
@@ -118,6 +119,9 @@
case 'w':
wipe = 1;
break;
+ case 'u':
+ real_uuid = 1;
+ break;
case 'z':
gzip = 1;
break;
@@ -227,7 +231,7 @@
}
exitcode = make_ext4fs_internal(fd, directory, mountpoint, fs_config_func, gzip,
- sparse, crc, wipe, sehnd, verbose, fixed_time, block_list_file);
+ sparse, crc, wipe, real_uuid, sehnd, verbose, fixed_time, block_list_file);
close(fd);
if (block_list_file)
fclose(block_list_file);
diff --git a/ext4_utils/uuid.c b/ext4_utils/uuid.c
deleted file mode 100644
index 33d2494..0000000
--- a/ext4_utils/uuid.c
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2010 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.
- */
-
-#include <string.h>
-
-#ifdef USE_MINGW
-#include <winsock2.h>
-#else
-#include <arpa/inet.h>
-#endif
-
-#include "ext4_utils.h"
-#include "sha1.h"
-#include "uuid.h"
-
-/* Definition from RFC-4122 */
-struct uuid {
- u32 time_low;
- u16 time_mid;
- u16 time_hi_and_version;
- u8 clk_seq_hi_res;
- u8 clk_seq_low;
- u16 node0_1;
- u32 node2_5;
-};
-
-static void sha1_hash(const char *namespace, const char *name,
- unsigned char sha1[SHA1_DIGEST_LENGTH])
-{
- SHA1_CTX ctx;
- SHA1Init(&ctx);
- SHA1Update(&ctx, (const u8*)namespace, strlen(namespace));
- SHA1Update(&ctx, (const u8*)name, strlen(name));
- SHA1Final(sha1, &ctx);
-}
-
-void generate_uuid(const char *namespace, const char *name, u8 result[16])
-{
- unsigned char sha1[SHA1_DIGEST_LENGTH];
- struct uuid *uuid = (struct uuid *)result;
-
- sha1_hash(namespace, name, (unsigned char*)sha1);
- memcpy(uuid, sha1, sizeof(struct uuid));
-
- uuid->time_low = ntohl(uuid->time_low);
- uuid->time_mid = ntohs(uuid->time_mid);
- uuid->time_hi_and_version = ntohs(uuid->time_hi_and_version);
- uuid->time_hi_and_version &= 0x0FFF;
- uuid->time_hi_and_version |= (5 << 12);
- uuid->clk_seq_hi_res &= ~(1 << 6);
- uuid->clk_seq_hi_res |= 1 << 7;
-}
diff --git a/ext4_utils/uuid.h b/ext4_utils/uuid.h
deleted file mode 100644
index ff1b438..0000000
--- a/ext4_utils/uuid.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2010 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 _UUID_H_
-#define _UUID_H_
-
-#include "ext4_utils.h"
-
-void generate_uuid(const char *namespace, const char *name, u8 result[16]);
-
-#endif
diff --git a/f2fs_utils/f2fs_sparseblock.c b/f2fs_utils/f2fs_sparseblock.c
index 950628c..e39a61f 100644
--- a/f2fs_utils/f2fs_sparseblock.c
+++ b/f2fs_utils/f2fs_sparseblock.c
@@ -262,13 +262,13 @@
struct f2fs_checkpoint *cp1, *cp2, *cur_cp;
int cur_cp_no;
- unsigned long blk_size;// = 1<<le32_to_cpu(info->sb->log_blocksize);
+ unsigned long blk_size;
unsigned long long cp1_version = 0, cp2_version = 0;
unsigned long long cp1_start_blk_no;
unsigned long long cp2_start_blk_no;
u32 bmp_size;
- blk_size = 1U<<le32_to_cpu(sb->log_blocksize);
+ blk_size = 1U << le32_to_cpu(sb->log_blocksize);
/*
* Find valid cp by reading both packs and finding most recent one.
@@ -489,7 +489,8 @@
u64 block;
unsigned int used, found, started = 0, i;
- for (block=startblock; block<info->total_blocks; block++) {
+ block = startblock;
+ while (block < info->total_blocks) {
/* TODO: Save only relevant portions of metadata */
if (block < info->main_blkaddr) {
if (func(block, data)) {
@@ -512,17 +513,24 @@
/* get SIT entry from SIT section */
if (!found) {
- sit_block_num_cur = segnum/SIT_ENTRY_PER_BLOCK;
+ sit_block_num_cur = segnum / SIT_ENTRY_PER_BLOCK;
sit_entry = &info->sit_blocks[sit_block_num_cur].entries[segnum % SIT_ENTRY_PER_BLOCK];
}
block_offset = (block - info->main_blkaddr) % info->blocks_per_segment;
+ if (block_offset == 0 && GET_SIT_VBLOCKS(sit_entry) == 0) {
+ block += info->blocks_per_segment;
+ continue;
+ }
+
used = f2fs_test_bit(block_offset, (char *)sit_entry->valid_map);
if(used)
if (func(block, data))
return -1;
}
+
+ block++;
}
return 0;
}
@@ -548,7 +556,7 @@
{
struct privdata *d = data;
char *buf;
- int pdone = (pos*100)/d->info->total_blocks;
+ int pdone = (pos * 100) / d->info->total_blocks;
if (pdone > d->done) {
d->done = pdone;
printf("Done with %d percent\n", d->done);
@@ -562,7 +570,7 @@
}
off64_t ret;
- ret = lseek64(d->outfd, pos*F2FS_BLKSIZE, SEEK_SET);
+ ret = lseek64(d->outfd, pos * F2FS_BLKSIZE, SEEK_SET);
if (ret < 0) {
SLOGE("failed to seek\n");
return ret;
diff --git a/verity/Utils.java b/verity/Utils.java
index 3576e3b..937c206 100644
--- a/verity/Utils.java
+++ b/verity/Utils.java
@@ -35,6 +35,8 @@
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;
@@ -52,6 +54,7 @@
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 {
@@ -63,10 +66,16 @@
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());
@@ -208,15 +217,36 @@
}
}
- private static String getSignatureAlgorithm(Key key) {
- if ("RSA".equals(key.getAlgorithm())) {
+ 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) {
+ static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) throws Exception {
String id = ALG_TO_ID.get(getSignatureAlgorithm(key));
if (id == null) {
diff --git a/verity/VerityVerifier.java b/verity/VerityVerifier.java
index 5c9d7d2..6b3f49e 100644
--- a/verity/VerityVerifier.java
+++ b/verity/VerityVerifier.java
@@ -20,31 +20,83 @@
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.PrivateKey;
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
*/
- public static int fromle(int value) {
+ private static int fromle(int value) {
byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
}
@@ -53,28 +105,51 @@
* Converts a 2-byte little endian value to Java a integer
* @param value Little endian short to convert
*/
- public static int fromle(short value) {
+ 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
*/
- public static RandomAccessFile openImage(String fname) throws Exception {
- File tmp = File.createTempFile("system", ".raw");
- tmp.deleteOnExit();
+ private void openImage(String fname) throws Exception {
+ image = File.createTempFile("system", ".raw");
+ image.deleteOnExit();
Process p = Runtime.getRuntime().exec("simg2img " + fname +
- " " + tmp.getAbsoluteFile());
+ " " + image.getAbsoluteFile());
p.waitFor();
if (p.exitValue() != 0) {
throw new IllegalArgumentException("Invalid image: failed to unsparse");
}
-
- return new RandomAccessFile(tmp, "r");
}
/**
@@ -106,56 +181,234 @@
}
/**
- * Reads and validates verity metadata, and check the signature against the
+ * 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 img File handle to the image file
* @param key Public key to use for signature verification
*/
- public static boolean verifyMetaData(RandomAccessFile img, PublicKey key)
+ public boolean verifyMetaData(PublicKey key)
throws Exception {
- img.seek(getMetadataPosition(img));
- int magic = fromle(img.readInt());
-
- if (magic != VERITY_MAGIC) {
- throw new IllegalArgumentException("Invalid image: verity metadata not found");
- }
-
- int version = fromle(img.readInt());
-
- if (version != VERITY_VERSION) {
- throw new IllegalArgumentException("Invalid image: unknown metadata version");
- }
-
- byte[] signature = new byte[VERITY_SIGNATURE_SIZE];
- img.readFully(signature);
-
- int tableSize = fromle(img.readInt());
-
- byte[] table = new byte[tableSize];
- img.readFully(table);
-
- return Utils.verify(key, table, signature,
+ 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 {
- if (args.length != 2) {
- System.err.println("Usage: VerityVerifier <sparse.img> <certificate.x509.pem>");
+ 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);
}
- Security.addProvider(new BouncyCastleProvider());
-
- X509Certificate cert = Utils.loadPEMCertificate(args[1]);
- PublicKey key = cert.getPublicKey();
- RandomAccessFile img = openImage(args[0]);
+ VerityVerifier verifier = new VerityVerifier(args[0]);
try {
- if (verifyMetaData(img, key)) {
+ if (verifier.verify(key)) {
System.err.println("Signature is VALID");
System.exit(0);
- } else {
- System.err.println("Signature is INVALID");
}
} catch (Exception e) {
e.printStackTrace(System.err);