この記事は、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 bootpart
Code language: Bash (bash)
initrd(ファイル名initrd.img
)とKernel(ファイル名Image
)それぞれの署名を作成するための署名鍵を作成します。
openssl genrsa -out kernel_sign.pem 2048
Code language: Bash (bash)
署名ファイルinitrd.img.sign
とImage.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.sign
とImage.sign
をu-bootで検証するためにこの署名の形式を知る必要があります。openssl dgst -sign
コマンドで作成される署名ファイルはPKCS#1 v1.5形式のパディングがされて秘密鍵で暗号化されるようです。PKCS#1 v1.5はRFC 2313で規定されるRSAのパディングフォーマットです。
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
を推奨
PS
はBT
が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 -lcrypto
Code language: Bash (bash)
作成したdec_sigに秘密鍵と署名を渡し検証鍵での復号結果を見てみます。
$ ./dec_sig kernel_sign.pem bootpart/boot/initrd.img.sign
0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d060960864801650304020105000420d0d7e2e1b4cd4fe22cc040ef0fc54e8e216904cf94a2ade384680c94f8b404ec
3031300d060960864801650304020105000420d0d7e2e1b4cd4fe22cc040ef0fc54e8e216904cf94a2ade384680c94f8b404ec
Code 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
の出力です。BT
は01
、PSはFF...FF
、00
に続いて、D
が3031300d060960864801650304020105000420d0d7e2e1b4cd4fe22cc040ef0fc54e8e216904cf94a2ade384680c94f8b404ec
です。
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 STRINGhttps://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]:D0D7E2E1B4CD4FE22CC040EF0FC54E8E216904CF94A2ADE384680C94F8B404EC
Code language: Bash (bash)
簡単にopenssl asn1parse
の出力について説明します。
x:
- オフセット、先頭からのバイト数を表します。
0:
と書かれている部分はオフセット0、つまり先頭を表しています。
- オフセット、先頭からのバイト数を表します。
d=x
- 深さ(depth)を表します。
openssl asn1parse
のオプションに-i
を追加して表示しているため、行の最後の表示(SEQUENCE
、OBJECT
など)が深さに応じてインデントされていることが分かります。
- 深さ(depth)を表します。
hl=x
- ヘッダー(header length)長を表します。
l=x
- コンテンツ長を表します。
bootpart/boot/initrd.img
のハッシュ値を計算してみます。
$ sha256sum bootpart/boot/initrd.img
d0d7e2e1b4cd4fe22cc040ef0fc54e8e216904cf94a2ade384680c94f8b404ec bootpart/boot/initrd.img
Code 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)
P3450-0000はJetson Nano、P3541-0000はJetson Nano 2GBを表すようです。
必要なライブラリがビルドがされるように以下をu-boot/configs/p3450-0000_defconfig
の最後に追加します。
CONFIG_RSA=y
CONFIG_RSA_SOFTWARE_EXP=y
CONFIG_SHA256=y
Code language: plaintext (plaintext)
u-boot/cmd/Makefile
に以下を追加します。
obj-y += secure_boot.o
Code 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_r
とkernel_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の公開鍵は通常n
とe
で構成されますが、key_prop
ではrr
とn0inv
という値も指定します。これらの値はrsa_mod_exp_sw
が内部でモンゴメリ乗算を使用するために必要となります。rr
とn0inv
はn
から計算可能な値で予めホストで作成しておきます。モンゴメリ乗算については別の記事で解説しようと思います。
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_rr
、rsa_N
、rsa_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.h
Code 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_padding
はu-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 mmcblk0p1
Code language: Bash (bash)
これでinitrdとKernelが認証され、rootfsを復号しJetson Nanoのセキュアブートに成功しました。
コードは準備中です。
以上です。