initrd and Kernel verification by u-boot

Jetson Nano

この記事は、Jetson Nano – Rootfs Encryptionの記事の続きです。

こちらの記事ではu-bootのカスタマイズ方法について解説しました。今回はu-bootをカスタマイズし、inirtrdとKernelを認証するコードを追加したいと思います。こちらの記事では、rootfsを復号マウントすることができていますので、initrdとKernelを認証し、rootfsの暗号化鍵を隠すことでrootfsを外部から復号されることを防ぐことができます。もしinitdとkernelが認証されないと、それらは改ざん可能であるため、例えば、rootfsの暗号化鍵をターミナルに表示するプログラムをinitrdに配置することが可能です。なおrootfsの暗号化鍵を隠す方法は別の記事で解説します。

環境構築

こちらの記事にあるように、u-bootはtegra-l4t-r32.7.1がチェックアウトされ、gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnuがセットアップされている状態とします。ホストPCはUbuntu20.04を使用します。作業はLinux_for_Tegraディレクトリで行います。Linux_for_Tegraディレクトリはこちらの記事を参照してください。

署名鍵と署名ファイルの作成

initrdとKernelを署名するための署名鍵を作成し、署名鍵を使用しinitrd、Kernelの署名ファイルを作成します。

まずこちらの記事で作成したbootloader/bootpart.imgをマウントします。

mkdir -p bootpart
sudo mount bootloader/bootpart.img bootpartCode language: Bash (bash)

initrd(ファイル名initrd.img)とKernel(ファイル名Image)それぞれの署名を作成するための署名鍵を作成します。

openssl genrsa -out kernel_sign.pem 2048Code language: Bash (bash)

署名ファイルinitrd.img.signImage.singを作成します。

sudo bash -c "openssl dgst -sign kernel_sign.pem -sha256 bootpart/boot/initrd.img > bootpart/boot/initrd.img.sign"
sudo bash -c "openssl dgst -sign kernel_sign.pem -sha256 bootpart/boot/Image > bootpart/boot/Image.sign"Code language: Bash (bash)

署名形式について

作成された、initrd.img.signImage.signをu-bootで検証するためにこの署名の形式を知る必要があります。openssl dgst -signコマンドで作成される署名ファイルはPKCS#1 v1.5形式のパディングがされて秘密鍵で暗号化されるようです。PKCS#1 v1.5はRFC 2313で規定されるRSAのパディングフォーマットです。

RFC 2313: PKCS #1: RSA Encryption Version 1.5

https://www.rfc-editor.org/rfc/rfc2313#section-8.1 に以下の記述があります。

8.1 Encryption-block formatting

   A block type BT, a padding string PS, and the data D shall be
   formatted into an octet string EB, the encryption block.

              EB = 00 || BT || PS || 00 || D .           (1)
https://www.rfc-editor.org/rfc/rfc2313#section-8.1
  • BTは秘密鍵操作の場合00もしくは01
    • BTは秘密鍵操作の場合01を推奨
  • PSBTが00の場合00、BTが01の場合FF
    • PSの長さは(鍵の長さ)-3-(Dの長さ)
  • Dの形式は後述

説明が長くなったので実際に確認してみたいと思います。以下は署名鍵(=秘密鍵)から検証鍵(=公開鍵)を取り出し、署名ファイルを復号し表示するプログラムです。ファイル名はdec_sig.cとします。

#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <string.h>
#include <stdio.h>

int usage(const char *cmd) {
    fprintf(stderr, "Error: %s private.pem signature.sig\n", cmd);
    return 1;
}

int load_data(const char *name, unsigned char *buf, size_t buf_len) {
    FILE *fp;
    size_t read_len;
    fp = fopen(name, "rb");
    if (fp == NULL) {
        return -1;
    }
    read_len = fread(buf, 1, buf_len, fp);
    fclose(fp);
    return read_len;
}

void dump(const unsigned char *data, size_t len) {
    for (size_t i = 0; i < len; i ++) {
        printf("%02x", data[i]);
    }
    printf("\n");
}

int main(int argc, char *argv[]) {
	FILE* fp;
    RSA *key;
    unsigned char buf[2048 / 8];
    unsigned char out[2048 / 8];
    size_t outlen;

    if (argc != 3) {
        return usage(argv[0]);
    }
    fp = fopen(argv[1], "rb");
    key = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);

    if (load_data(argv[2], buf, sizeof(buf)) < 0) {
        return usage(argv[0]);
    }

    // dump all decrypted data
    memset(out, 0, sizeof(out));
    outlen = RSA_public_decrypt(sizeof(buf), buf, out, key, RSA_NO_PADDING);
    if (outlen < 0) {
        return usage(argv[0]);
    }
    dump(out, outlen);

    // dump D decrypted data
    memset(out, 0, sizeof(out));
    outlen = RSA_public_decrypt(sizeof(buf), buf, out, key, RSA_PKCS1_PADDING);
    if (outlen < 0) {
        return usage(argv[0]);
    }
    dump(out, outlen);
}Code language: C++ (cpp)

コンパイルします。

gcc test_rsa.c -o dec_sig -lcryptoCode language: Bash (bash)

作成したdec_sigに秘密鍵と署名を渡し検証鍵での復号結果を見てみます。

$ ./dec_sig kernel_sign.pem bootpart/boot/initrd.img.sign
0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d060960864801650304020105000420d0d7e2e1b4cd4fe22cc040ef0fc54e8e216904cf94a2ade384680c94f8b404ec
3031300d060960864801650304020105000420d0d7e2e1b4cd4fe22cc040ef0fc54e8e216904cf94a2ade384680c94f8b404ecCode language: Bash (bash)

2行表示されました。1行目は0001ff...、2行名は303130...です。dec_sig.cを見ると分かりますが、1行目はRSA_public_decryptのオプションにRSA_NO_PADDINGを指定した出力結果、2行目はRSA_PKCS1_PADDINGを指定した出力結果になります。

RSA_NO_PADDINGの出力結果:

0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d060960864801650304020105000420d0d7e2e1b4cd4fe22cc040ef0fc54e8e216904cf94a2ade384680c94f8b404ec

これは、00 || BT || PS || 00 || Dの出力です。BT01、PSはFF...FF00に続いて、D3031300d060960864801650304020105000420d0d7e2e1b4cd4fe22cc040ef0fc54e8e216904cf94a2ade384680c94f8b404ecです。

RSA_PKCS1_PADDINGの出力結果:

3031300d060960864801650304020105000420d0d7e2e1b4cd4fe22cc040ef0fc54e8e216904cf94a2ade384680c94f8b404ec

これは、Dそのものです。RSA_NO_PADDINGは署名を単に復号し、RSA_PKCS1_PADDINGは署名を復号し00 || BT || PS || 00を取り除いてDを出力することが分かります。

Dをもう少し見ています。

Dの形式は https://www.rfc-editor.org/rfc/rfc2313#section-10.1.2 にあるように、以下の形式であることが分かります。

10.1.2 Data encoding

   The message digest MD and a message-digest algorithm identifier shall
   be combined into an ASN.1 value of type DigestInfo, described below,
   which shall be BER-encoded to give an octet string D, the data.

   DigestInfo ::= SEQUENCE {
     digestAlgorithm DigestAlgorithmIdentifier,
     digest Digest }

   DigestAlgorithmIdentifier ::= AlgorithmIdentifier

   Digest ::= OCTET STRING
https://www.rfc-editor.org/rfc/rfc2313#section-10.1.2

https://www.rfc-editor.org/rfc/rfc3447#appendix-A.2.4 にsha256のAlgorithmIdentifierの記述があります。

      DigestAlgorithm ::=
          AlgorithmIdentifier { {PKCS1-v1-5DigestAlgorithms} }

      PKCS1-v1-5DigestAlgorithms    ALGORITHM-IDENTIFIER ::= {
          { OID id-md2 PARAMETERS NULL    }|
          { OID id-md5 PARAMETERS NULL    }|
          { OID id-sha1 PARAMETERS NULL   }|
          { OID id-sha256 PARAMETERS NULL }|
          { OID id-sha384 PARAMETERS NULL }|
          { OID id-sha512 PARAMETERS NULL }
      }
https://www.rfc-editor.org/rfc/rfc3447#appendix-A.2.4

また https://www.rfc-editor.org/rfc/rfc3447#section-9.2 にsha256を使用したときの実際のバイト列が書かれています。Hはハッシュ値です。

      SHA-256: (0x)30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00
                   04 20 || H.
https://www.rfc-editor.org/rfc/rfc3447#section-9.2

実際にDをバイナリに変換しopenssl ans1parseで出力してみると以下の結果が得られます。

$ echo -n 3031300d060960864801650304020105000420d0d7e2e1b4cd4fe22cc040ef0fc54e8e216904cf94a2ade384680c94f8b404ec | xxd -r -p > D.bin
$ openssl asn1parse -in D.bin -inform der -i
    0:d=0  hl=2 l=  49 cons: SEQUENCE          
    2:d=1  hl=2 l=  13 cons:  SEQUENCE          
    4:d=2  hl=2 l=   9 prim:   OBJECT            :sha256
   15:d=2  hl=2 l=   0 prim:   NULL              
   17:d=1  hl=2 l=  32 prim:  OCTET STRING      [HEX DUMP]:D0D7E2E1B4CD4FE22CC040EF0FC54E8E216904CF94A2ADE384680C94F8B404ECCode language: Bash (bash)

簡単にopenssl asn1parseの出力について説明します。

  • x:
    • オフセット、先頭からのバイト数を表します。0:と書かれている部分はオフセット0、つまり先頭を表しています。
  • d=x
    • 深さ(depth)を表します。openssl asn1parseのオプションに-iを追加して表示しているため、行の最後の表示(SEQUENCEOBJECTなど)が深さに応じてインデントされていることが分かります。
  • hl=x
    • ヘッダー(header length)長を表します。
  • l=x
    • コンテンツ長を表します。

bootpart/boot/initrd.imgのハッシュ値を計算してみます。

$ sha256sum bootpart/boot/initrd.img
d0d7e2e1b4cd4fe22cc040ef0fc54e8e216904cf94a2ade384680c94f8b404ec  bootpart/boot/initrd.imgCode language: Bash (bash)

openssl asn1parseの出力フォーマットがRFCの説明と一致しました。またdec_sigで出力した結果が期待通りであることを確認しました。

検証の実装

OpenSSL dgst -signで生成される署名ファイルの中身が分かりましたので、u-bootに検証するコードを実装していきたいと思います。

u-bootのソースコードを見ていくと、u-boot/lib/rsa/rsa-verify.cというファイルに、rsa_verifyという関数があることが分かります。この関数をそのまま使えれば良いのですが、今回の目的には不要な処理がいくつかあるため、この関数が使用している処理のいくつかをコピーして新規に検証関数を作成したいと思います。

u-boot/include/configs/p3450-0000.hの最後に以下の行を追加し、起動時に独自のsecure_bootコマンドを実行するように修正します。

#if defined CONFIG_BOOTCOMMAND
  #undef CONFIG_BOOTCOMMAND
#endif

#define CONFIG_BOOTCOMMAND "<span style="background-color: initial; font-family: inherit; font-size: inherit; white-space: pre-wrap;">secure_boot</span>"Code language: C++ (cpp)

必要なライブラリがビルドがされるように以下をu-boot/configs/p3450-0000_defconfigの最後に追加します。

CONFIG_RSA=y
CONFIG_RSA_SOFTWARE_EXP=y
CONFIG_SHA256=yCode language: plaintext (plaintext)

u-boot/cmd/Makefileに以下を追加します。

obj-y += secure_boot.oCode language: Makefile (makefile)

u-boot/cmd/secure_boot.cを作成する準備ができました。

u-boot/cmd/secure_boot.cは以下のとおりです。

#include <common.h>
#include <command.h>
#include <env.h>
#include <fs.h>
#include <mapmem.h>
#include "pxe_utils.h"
#include "u-boot/rsa-mod-exp.h"
#include "u-boot/rsa.h"
#include "u-boot/sha256.h"
#include "rsa_key.h"
#include "image.h"

#define INITRD_FILE "/boot/initrd.img"
#define KERNEL_FILE "/boot/Image"
#define BOOT_DEV_PART "1:2"

#define BOOT_ARGS \
	"tegraid=21.1.2.0.0 ddr_die=4096M@2048M section=512M " \
	"memtype=0 vpr_resize usb_port_owner_info=0 lane_owner_info=0 emc_max_dvfs=0 " \
	"touch_id=0@63 video=tegrafb no_console_suspend=1 console=ttyS0,115200n8 " \
	"debug_uartport=lsport,4 earlyprintk=uart8250-32bit,0x70006000 maxcpus=4 " \
	"usbcore.old_scheme_first=1 lp0_vec=0x1000@0xff780000 core_edp_mv=1075 " \
	"core_edp_ma=4000 gpt tegra_fbmem=0x800000@0x92ca9000 is_hdmi_initialised=1 " \
	"earlycon=uart8250,mmio32,0x70006000 cryptdevice=/dev/mmcblk0p1:luks " \
	"root=/dev/mapper/luks rw rootwait rootfstype=ext4 console=ttyS0,115200n8 " \
	"fbcon=map:0 net.ifnames=0 " \
	"cryptopts=keyscript=/scripts/key.sh,source=/dev/mmcblk0p1,target=luks" \

static int rsa_verify_padding(const uint8_t *msg, const int len)
{
	int ff_len;
	int ret;
	/* sha256 specific der */
	const unsigned char der[] = {0x30,0x31,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01,0x05,0x00,0x04,0x20};

	/* first byte must be 0x00 */
	ret = *msg++;
	/* second byte must be 0x01 */
	ret |= *msg++ ^ 0x01;
	/* next ff_len bytes must be 0xff */
	ff_len = len - 2 - sizeof(der) - 1 - SHA256_SUM_LEN;
	for (int i = 0; i < ff_len; i ++) {
		ret |= *msg++ ^ 0xff;
	}
	/* next byte must be 0x00 */
	ret |= *msg++;
	/* next der_len bytes must match der_prefix */
	for (int i = 0; i < sizeof(der); i ++) {
		ret |= *msg++ ^ der[i];
	}
	return ret;
}

static int memcmp_consttime(const uint8_t *s1, const uint8_t *s2, size_t n)
{
	volatile int r = 0;
	for (volatile size_t i = 0; i < n; i ++) {
		r |= s1[i] ^ s2[i];
	}
	return r;
}

static int get_file(const char *filename, ulong addr, loff_t *actread, int verify)
{
	int ret;
	loff_t len;

	if (fs_set_blk_dev("mmc", BOOT_DEV_PART, FS_TYPE_EXT)) {
		printf("fs_set_blk_dev failed\n");
		return 1;
	}

	ret = fs_read(filename, addr, 0, 0, &len);
	if (ret < 0) {
		printf("fs_read failed %s: %d\n", INITRD_FILE, ret);
		return 1;
	}
	printf("%s: %llu bytes read in\n", filename, len);

	*actread = len;
	if (!verify) {
		return 0;
	}

	if (fs_set_blk_dev("mmc", BOOT_DEV_PART, FS_TYPE_EXT)) {
		printf("fs_set_blk_dev failed\n");
		return 1;
	}

	char filename_sign[256];
	uint8_t sign[RSA2048_BYTES];
	strncpy(filename_sign, filename, sizeof(filename_sign) - strlen(".sign") - 1);
	strcat(filename_sign, ".sign");

	ret = fs_read(filename_sign, (ulong)sign, 0, sizeof(sign), &len);
	if (ret < 0) {
		printf("fs_read failed %s: %d\n", INITRD_FILE, ret);
		return 1;
	}
	printf("%s: %llu bytes read in\n", filename_sign, len);

	uint8_t out[RSA2048_BYTES];
	struct key_prop key_prop;
	key_prop.rr = rsa_rr;
	key_prop.modulus = rsa_N;
	key_prop.public_exponent = NULL;
	key_prop.n0inv = rsa_n0inv;
	key_prop.num_bits = RSA_MIN_KEY_BITS;
	key_prop.exp_len = 0;
	memset(out, 0, sizeof(out));
	ret = rsa_mod_exp_sw(sign, sizeof(sign), &key_prop, out);
	if (ret) {
		printf("rsa_mod_exp_sw %d\n", ret);
		return ret;
	}
	for (int i = 0; i < sizeof(out); i ++) {
		printf("%02x", out[i]);
	}
	uint8_t hash[SHA256_SUM_LEN];
	sha256_csum_wd((uint8_t*)addr, *actread, hash, 0);

	ret = rsa_verify_padding(out, sizeof(out));
	ret |= memcmp_consttime(&out[sizeof(out) - sizeof(hash)], hash, sizeof(hash));
	if (ret) {
		printf("hash verification failed\n");
		return 1;
	}

	return 0;
}

static int do_secure_boot(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	uint32_t ramdisk_addr, kernel_addr;
	ramdisk_addr = simple_strtoul(env_get("ramdisk_addr_r"), NULL, 16);
	kernel_addr = simple_strtoul(env_get("kernel_addr_r"), NULL, 16);
	loff_t ramdisk_size;
	loff_t kernel_size;

	if (get_file(INITRD_FILE, ramdisk_addr, &ramdisk_size, 1)) {
		return 1;
	}

	if (get_file(KERNEL_FILE, kernel_addr, &kernel_size, 1)) {
		return 1;
	}

	char *bootm_argv[] = { "bootm", NULL, NULL, NULL, NULL };
	int bootm_argc = 4;
	char initrd_str[28];
	char initrd_size_str[17];

	bootm_argv[1] = env_get("kernel_addr_r");

	bootm_argv[2] = initrd_str;
	sprintf(initrd_size_str, "%lx", (ulong)ramdisk_size);
	strncpy(bootm_argv[2], env_get("ramdisk_addr_r"), 18);
	strcat(bootm_argv[2], ":");
	strncat(bootm_argv[2], initrd_size_str, 9);

	bootm_argv[3] = env_get("fdt_addr");

	env_set("bootargs", BOOT_ARGS);

	printf("bootm_argv[0]:%s\n",bootm_argv[0]);
	printf("bootm_argv[1]:%s\n",bootm_argv[1]);
	printf("bootm_argv[2]:%s\n",bootm_argv[2]);
	printf("bootm_argv[3]:%s\n",bootm_argv[3]);
	printf("bootargs:%s\n",BOOT_ARGS);

	ulong kern_addr;
	void *buf;	  

	kern_addr = genimg_get_kernel_addr(bootm_argv[1]);
	buf = map_sysmem(kern_addr, 0);
	/* Try bootm for legacy and FIT format image */
	if (genimg_get_format(buf) != IMAGE_FORMAT_INVALID)
		do_bootm(NULL, 0, bootm_argc, bootm_argv);
#ifdef CONFIG_CMD_BOOTI
	/* Try booting an AArch64 Linux kernel image */
	else
		do_booti(NULL, 0, bootm_argc, bootm_argv);
#elif defined(CONFIG_CMD_BOOTZ)
	/* Try booting a Image */
	else
		do_bootz(NULL, 0, bootm_argc, bootm_argv);
#endif
	unmap_sysmem(buf);
	return 0;
}

U_BOOT_CMD(secure_boot, 1, 1, do_secure_boot,
	"command to secure boot",
	""
);Code language: C++ (cpp)

secure_boot関数から説明します。

env_get("ramdisk_addr_r")env_get("kernel_addr_r")でinitrdとKernelをロードするアドレスを取得します。

ramdisk_addr_rkernel_addr_rはそれぞれinclude/configs/tegra210-common.hで定義されています。

static int do_secure_boot(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	uint32_t ramdisk_addr, kernel_addr;
	ramdisk_addr = simple_strtoul(env_get("ramdisk_addr_r"), NULL, 16);
	kernel_addr = simple_strtoul(env_get("kernel_addr_r"), NULL, 16);
	loff_t ramdisk_size;
	loff_t kernel_size;Code language: C++ (cpp)

get_file関数でinitrdとKernelをメモリにロードし認証を行います。

	if (get_file(INITRD_FILE, ramdisk_addr, &ramdisk_size, 1)) {
		return 1;
	}

	if (get_file(KERNEL_FILE, kernel_addr, &kernel_size, 1)) {
		return 1;
	}Code language: C++ (cpp)

get_file関数を説明します。get_file関数はデバイスからファイルを読み込み、認証を行う関数です。initrdとKernelが保存されているパーティションは非暗号化パーティションでid=2が指定されていますので、BOOT_DEV_PART"1:2"を指定しています。詳細はこちらの記事を参照してください。

static int get_file(const char *filename, ulong addr, loff_t *actread, int verify)
{
	int ret;
	loff_t len;

	if (fs_set_blk_dev("mmc", BOOT_DEV_PART, FS_TYPE_EXT)) {
		printf("fs_set_blk_dev failed\n");
		return 1;
	}

	ret = fs_read(filename, addr, 0, 0, &len);
	if (ret < 0) {
		printf("fs_read failed %s: %d\n", INITRD_FILE, ret);
		return 1;
	}
	printf("%s: %llu bytes read in\n", filename, len);

	*actread = len;
	if (!verify) {
		return 0;
	}
Code language: C++ (cpp)

次に、filename".sign"を連結したファイル名をバッファsignに読み出します。

	char filename_sign[256];
	uint8_t sign[RSA2048_BYTES];
	strncpy(filename_sign, filename, sizeof(filename_sign) - strlen(".sign") - 1);
	strcat(filename_sign, ".sign");

	ret = fs_read(filename_sign, (ulong)sign, 0, sizeof(sign), &len);
	if (ret < 0) {
		printf("fs_read failed %s: %d\n", INITRD_FILE, ret);
		return 1;
	}
	printf("%s: %llu bytes read in\n", filename_sign, len);Code language: C++ (cpp)

次にkey_propに公開鍵を登録し、rsa_mod_exp_swで署名ファイルを復号します。この処理は上で説明した、RSA_public_decryptのオプションにRSA_NO_PADDINGを指定したときと同等の処理を行います。

RSAの公開鍵は通常neで構成されますが、key_propではrrn0invという値も指定します。これらの値はrsa_mod_exp_swが内部でモンゴメリ乗算を使用するために必要となります。rrn0invnから計算可能な値で予めホストで作成しておきます。モンゴメリ乗算については別の記事で解説しようと思います。

	uint8_t out[RSA2048_BYTES];
	struct key_prop key_prop;
	key_prop.rr = rsa_rr;
	key_prop.modulus = rsa_N;
	key_prop.public_exponent = NULL;
	key_prop.n0inv = rsa_n0inv;
	key_prop.num_bits = RSA_MIN_KEY_BITS;
	key_prop.exp_len = 0;
	memset(out, 0, sizeof(out));
	ret = rsa_mod_exp_sw(sign, sizeof(sign), &key_prop, out);
	if (ret) {
		printf("rsa_mod_exp_sw %d\n", ret);
		return ret;
	}Code language: C++ (cpp)

rsa_rrrsa_Nrsa_n0invの計算は https://github.com/otamajakusi/android-dumpkey を使用します。C言語のヘッダー形式でそれぞれの値を出力します。

openssl rsa -in kernel_sign.pem -pubout -out kernel_verify.pem
git clone https://github.com/otamajakusi/android-dumpkey.git
./android-dumpkey/dumppublickey kernel_verify.pem > u-boot/cmd/rsa_key.hCode language: Bash (bash)

最後に復号後のデータを検証します。

sha256_csum_wdでファイルのsha256ハッシュ値を計算し、バッファhashに格納します。rsa_verify_paddingでPKCS#1 v1.5パディングを確認し、最後memcmp_consttimeでsha256ハッシュ値を確認します。

	uint8_t hash[SHA256_SUM_LEN];
	sha256_csum_wd((uint8_t*)addr, *actread, hash, 0);

		printf("hash verification failed\n");
		return 1;
	}
	ret = rsa_verify_padding(out, sizeof(out));
	ret |= memcmp_consttime(&out[sizeof(out) - sizeof(hash)], hash, sizeof(hash));
	if (ret) {
		printf("hash verification failed\n");
		return 1;
	}Code language: C++ (cpp)

rsa_verify_paddingu-boot/lib/rsa/rsa-verify.cで使用されているものとほぼ同じ実装です。rsa_verify_paddingの実装やmemcmp_consttimeの実装は、リターン値rにビット論理和演算子(|=)で結果を保存し最後にrをリターンします。署名データによって処理時間が変わらないようにこのような実装になっています。今回は公開鍵と署名の復号結果は攻撃者は入手できるためタイミングによる攻撃は難しいですが、一般的に署名の検証は署名データによらず常に一定の時間がかかるようにします。CRYPTO_memcmpという関数も同様の目的です。

最後にu-bootをビルドし書き込みを行います。bootloader/system.imgが更新されないように./flash.shには-rオプションをつけて書き込みます。

cd u-boot
export CROSS_COMPILE=/opt/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
make distclean
make p3541-0000_defconfig
make -j
cp u-boot{,.bin,.dtb,-dtb.bin} ../bootloader/t210ref/p3450-0000/
cd ..
sudo ./flash.sh -r -x 0x21 jetson-nano-qspi mmcblk0p1Code language: Bash (bash)

これでinitrdとKernelが認証され、rootfsを復号しJetson Nanoのセキュアブートに成功しました。

コードは準備中です。

以上です。