README.md 18.1 KB
Newer Older
Stefano Babic's avatar
Stefano Babic committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# OS Security

## Secure Boot

Secure boot ensures only authenticated software runs on the device and is achieved by verifying digital signatures of the software prior to executing the code. 
To achieve secure boot SoC support is required. NXP i.MX processors family has support for it, this feature is marketed as High Assurance Boot (HAB).

### Build a chain of Trust

A typical Linux based system has the following components:

* Bootloader

* Kernel, Device Tree

* Root Filesystem + User applications

* Application data

#### Bootloader Authentication

Bootloader Authentication is processor specific. However the mechanisms are the same :

* Creating a public/private key pair
* Signing the bootloader using vendor-specific code signing tools (CST Tool for NXP)
* Burning the public key (or hash of public key) onto One-Time programmable (OTP) fuse on the processor during Production

The processor Boot-ROM code on power-on loads the bootloader with the signature/certificates appended to it,
then authenticates the software by performing the following steps:

* Verify the public key used in the signature/certificate with the one stored in the OTP fuses
* Extract the hash of bootloader from the signature using the verified public key
* Compare the extracted hash with the computed hash of the bootloader. If it matches the boot process is continued if not it stops


![HAB Authentication](HAB1.png)


##### NXP Signing Tool (CST)

CST Tool can be downloaded from NXP Website at :
https://www.nxp.com/webapp/sps/download/license.jsp?colCode=IMX_CST_TOOL

44
45
46
47
For imx8mp
https://www.nxp.com/webapp/Download?colCode=IMX_CST_TOOL_NEW

which was ```cst-3.3.1.tgz``` when writting this document.
Stefano Babic's avatar
Stefano Babic committed
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

* Certificates/keys generation:

create a serial number file that OpenSSL wil use for the certificate serial number:

        $ cd cst-2.3.3/keys
        $ echo "12345678" > serial

create a key pass file that contains a passphrase to protect the HAB code signing private keys:

        $ cd cst-2.3.3/keys
        $ echo 'xm5hg86s$ps' >  key_pass.txt
        $ echo 'xm5hg86s$ps' >> key_pass.txt

create the signature keys/certificates for hab4:

        cst-2.3.3/keys$ ./hab4_pki_tree.sh

       ...
       Do you want to use an existing CA key (y/n)?: n
       Do you want to use Elliptic Curve Cryptography (y/n)?: n
       Enter key length in bits for PKI tree: 4096
       Enter PKI tree duration (years): 10
       How many Super Root Keys should be generated? 4
       Do you want the SRK certificates to have the CA flag set? (y/n)?: y

generate the fuse table and binary hash:

        $ cd cst-2.3.3/crts
        $ ../linux64/bin/srktool -h 4 -t SRK_1_2_3_4_table.bin -e SRK_1_2_3_4_fuse.bin -d sha256 -c \
        SRK1_sha256_4096_65537_v3_ca_crt.pem,./SRK2_sha256_4096_65537_v3_ca_crt.pem,./SRK3_sha256_4096_65537_v3_ca_crt.pem,./SRK4_sha256_4096_65537_v3_ca_crt.pem -f 1
        
        
81
82
###### IMX.6 based boards

Stefano Babic's avatar
Stefano Babic committed
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
The hashes table that must be burned into the device to validate the public keys, can be generated from the fuse table above using the script:

        #!/bin/bash
        #
        # For iMX6 there are 3 banks available
        #
        bank=3
        word=0
        if [ $# -ne 1 ]; then
                echo Usage: $0  /path/to/SRK_1_2_3_4_fuse.bin
                exit 1
        fi
        for ((i=0; i<8; i++))
        do
                offset=$((i * 4))
                printf "fuse prog -y $bank $word %s\n" `hexdump -s $offset -n 4  -e '/4 "0x"' -e '/4 "%X""\n"' ${1}`
                ((word++))
        done

 a call example :
 
        imx6-hab-cst-sign$ ./imx6-u-boot_fuse_commands.sh /path/to/SRK_1_2_3_4_fuse.bin
        fuse prog -y 3 0 0x85CB70D5
        fuse prog -y 3 1 0xE3064103
        fuse prog -y 3 2 0xF372C459
        fuse prog -y 3 3 0x94C7ECBD
        fuse prog -y 3 4 0x3A98FD08
        fuse prog -y 3 5 0xFBFC10C4
        fuse prog -y 3 6 0x3007BA2B
        fuse prog -y 3 7 0xDED88E4C 
        
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
###### IMX.8 based boards

get the values for the fuses with

	$ hexdump -e '/4 "0x"' -e '/4 "%X""\n"' < SRK_1_2_3_4_fuse.bin
	0x85CB70D5
	0xE3064103
	0xF372C459
	0x94C7ECBD
	0x3A98FD08
	0xFBFC10C4
	0x3007BA2B
	0xDED88E4C

which leads in the following U-Boot commands:

	=> fuse prog -y 6 0 0x85CB70D5
	=> fuse prog -y 6 1 0xE3064103
	=> fuse prog -y 6 2 0xF372C459
	=> fuse prog -y 6 3 0x94C7ECBD
	=> fuse prog -y 7 0 0x3A98FD08
	=> fuse prog -y 7 1 0xFBFC10C4
	=> fuse prog -y 7 2 0x3007BA2B
	=> fuse prog -y 7 3 0xDED88E4C

Once it's *absolutely* sure about what has been done so far and that it works, you can “close” the device.
Stefano Babic's avatar
Stefano Babic committed
140
141
142

This step is IRREVERSIBLE, better make sure there is no HAB Events in open mode configuration!!!!

143
144
imx.6 case:

Stefano Babic's avatar
Stefano Babic committed
145
146
        fuse prog 0 6 0x2

147
148
149
150
imx.8 case:

	=> fuse prog 1 3 0x02000000

Stefano Babic's avatar
Stefano Babic committed
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
* Signing Process:

The first stage of HAB is the authentication of U-Boot. CST tool is used to generate the CSF data, which includes public
key, certificate, and instruction of authentication process. The CSF data is attached to the original u-boot.img. The process is
called Signature.
The IVT should be modified to contain a pointer to the CSF data. The original u-boot.img image size is around 0x27000 to
0x28000. For convenience, we first extend its size to 0x2F000 (with fill 0x5A). Then concatenate it with the CSF data. The
combined image is again extended to a fixed length (0x31000), which is used as the IVT image size parameter.
The new memory layout is shown in the following image layout:

![HAB Layout](HAB2.png)


This process is fully automated in Yocto by using the class uboot-hab-sign.bbclass which is using the default settings:


       # HAB Settings
       HAB_ENABLE= "1"
       HAB_DIR ?= "${HAB_BASE}/conf/hab"
       SRKTAB ?= "${HAB_DIR}/crts/SRK_1_2_3_4_table.bin"
       CSFK ?= "${HAB_DIR}/crts/CSF1_1_sha256_4096_65537_v3_usr_crt.pem"
       SIGN_CERT ?= "${HAB_DIR}/crts/IMG1_1_sha256_4096_65537_v3_usr_crt.pem"

U-Boot is signed then with developement Certificates/keys, For production the local.conf can be used to overwrite those settings by the productive ones.

176
177
178
179
180
181
182
183
The outcome of the process is:

| Image  | imx6 | imx8 |
|--------|------|------|
| SPL    | SPL.signed | SPL.signed |
| U-Boot | u-boot-ivt.img-spi.signed | u-boot.itb.signed |

:warning: In imx8 case, you need to build U-Boot with SPL_FIT_GENERATOR disabled. See more infos [i.MX8M HAB](imx8_hab.md)
Stefano Babic's avatar
Stefano Babic committed
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218

## Signed Filesystem

To extend the chain of Trust to the Applications, the Root Filesystem must be authenticated on each boot to check it's validity using Digital Signature Verification.
there are several way to achieve this goal such as dm-verity, dm-integrity, IMA/EVM,...But we chose a simple way to implement it by verifying the wohl Rootfs Partition Signature 
in Initramfs before mounting it:


        # +------------+  0x0                     -
        # |            |                          |
        # |            |                          |
        # |            |                          |
        # |            |                          |
        # | File       |                          |
        # | System     |                          |
        # .            |                          |
        # .            |                           > PAYLOAD to be signed      ----+
        # .            |                          |                                |
        # |            |                          |                                |
        # |            |                          |                                |
        # +------------+                          |                                |
        # |            |                          |                                |
        # | Fill Data  |                          |                                |
        # |            |                          |                                |
        # +------------+ MAGIC_OFFSET(MB Aligned) |                                |
        # | Magic      |                          |                                |
        # +------------+ MAGIC_OFFSET + MAGIC_LEN -                                |
        # |            |                                                           |
        # | Signature  | <---------------------------------------------------------+
        # |            |
        # +------------+


1) The rootfs image size is aligned to MB blocks

219
2) Magic Header is appended to those blocks
Stefano Babic's avatar
Stefano Babic committed
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275

3) The sum is then hashed signed and signature is the appended


The class *sign-fs.bbclass*

is automatically performing this process and is using the default settings:


        CA_CERT ?= "${HAB_DIR}/crts/CA1_sha256_4096_65537_v3_ca_crt.pem"
        ROOT_CERT ?= "${HAB_DIR}/crts/SRK1_sha256_4096_65537_v3_ca_crt.pem"
        SIGN_KEY ?= "${HAB_DIR}/keys/IMG1_1_sha256_4096_65537_v3_usr_key.pem"
        PASS_FILE ?= "${HAB_DIR}/keys/key_pass.txt"

added to machine conf or local conf will generate the signed rootfs in deploy folder:

        IMAGE_FSTYPES = "squashfs.signed"

On boot the initramfs is performing the reverse process to verify the digital signature of the rootfs:


        # +------------+  0x0 (MMC_PART_START) ----
        # |            |                          |
        # |            |                          |
        # |            |                          |
        # |            |                          |
        # | File       |                          |
        # | System     |                          |
        # .            |                          |
        # .            |                           > PAYLOAD to be verified    ----+
        # .            |                          |                                |
        # |            |                          |                                |
        # |            |                          |                                |
        # +------------+                          |                                |
        # |            |                          |                                |
        # | Fill Data  |                          |                                |
        # |            |                          |                                |
        # +------------+ MAGIC_OFFSET(MB Aligned) |                                |
        # | Magic      |                          |                                |
        # +------------+ MAGIC_OFFSET + MAGIC_LEN -                                |
        # |            |                                                           |
        # | Signature  | <---------------------------------------------------------+
        # |            |
        # +------------+


### encrypted rootfs (read only squashfs)

As rootfs we use a read-only squashfs (ramdisk). The rootfs is crypted
with dm-crypt with AES-128-GCM.

The class *crypt-fs.bbclass*
performs the needed steps to crypt the FS image.

As it is not a good idea to store the key in plain, we use the
DCP or CAAM for crypting the raw key and get a key blob we can store
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
in the SPI NOR.

The blob is stored on a project specific device. The user of the layer
should provide a /etc/default/initramfs file, that loads the blob
and copies it into /tmp/key.blob. This is used by the ramdisk to setup
the encryptded rootfs.

To create and store the blob:

	keyctl add symmetric "rootfskey" "<engine> load_plain <key hex text>" @s

For example:

	keyctl add symmetric "rootfskey" "caam load_plain 0102030405060708AABBCCDDEEFF12AA" @s
	keyctl show -x

You get an id for the key.

Then export the blob:

	keyctl pipe <key id> > /tmp/blob

And copy the resulting blob on a persistent device. The real key is never exposed and it is
decrypted with the unique key of the device, so this procedure must be done for each produced
device in the factory.
Stefano Babic's avatar
Stefano Babic committed
301

302
303
304
305
306
307
308
309
#### raw key definitions

define for example your raw key for the rootfs image:

ENC_KEY_RAW ?= "fdf6842566d47e47d6874da561fec433"

so the ```crypt-fs``` can use it. This key *never* should be lost!

Stefano Babic's avatar
Stefano Babic committed
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
### DCP usage

see implementation details in *dcp_overview.pdf*

### order of execution

We first crypt the FS image, than sign it, so on boot we first
check the signature and if the signature is OK, we encrypt the
FS image.

See the initrd script: *recipes-core/initrdscripts/files/initramfs-init.sh*

### setup

Intentionally using this meta layer as it is will fail your build.

You must add the keys for HAB boot, signed FS somehow to your yocto build
setup.

This can be done by simply adding another meta layer, which contains all the needed
keys.

Add this meta layer to your build and you use the default keys from there.

Of course, do not use them in your product!
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540

# Example usage of `meta-secure-imx` layer with imx6ull based BSP

## Introduction
This section of the documentation describes the integration of this meta layer
with BSP meta layer.

It is important to also use the `meta-security` and `meta-mainline-common` from
[1] to get the newest stable kernels.

## Available initramfs images:
1. `factory-image-fit` - it is responsible for the factory setup of the iMX
based board:
  - Fuse MAC address from tftp accessible `imx6ull-<board>-mac.txt`
  - Fuse SRK from tftp accessible `SRK_1_2_3_4_fuse.bin`
  - Create rootfs encryption blob - it needs to download (temporarily only)
	the `imx6ull-<board>-rootfs-enc-key.txt` with encryption key (it is the same
	as the one passed to `ENC_KEY_RAW` and will **NOT** be stored on the board)
  - Enable secure booting (a.k.a locking the board) -> PERMANENT
  - Sets the eMMC boot areas as RO (read only) -> PERMANENT.

It needs to be build separately with: `bitbake factory-image-fit`

2. `crypt-image-initramfs` is the fitImage with initramfs (and kernel), which:
  - Will read the keyblob from persistent memory
  - Setup dm-crypt to decrypt the rootfs
  - Switch root to boot from it

It is build when `virtual/kernel` is build (the
`INITRAMFS_IMAGE = "crypt-image-initramfs"` is added to machine.conf file)

## Enhancements to `meta-<board>-bsp` layer

1. `recipes-core/images/factory-image-fit.bbappend` - set correct load address
for the imx6ull SoC

```
do_assemble_fit_prepend() {
	sed -i "s|ITS_KERNEL_LOAD_ADDR|0x87800000|g" ${B}/rescue.its.in
	sed -i "s|ITS_KERNEL_ENTRY_ADDR|0x87800000|g" ${B}/rescue.its.in
}
```

2. `recipes-core/initrdscripts/initramfs-init_%.bbappend`
```
FILESEXTRAPATHS_prepend := "${THISDIR}/files:"

SRC_URI += " \
  file://initramfs \
"

INITFUNCTIONS_INSTALL_DIR ?= "${sysconfdir}/default"

do_install_append () {
	install -d ${D}${sysconfdir}
	install -d ${D}${INITFUNCTIONS_INSTALL_DIR}
	install -m 0755 ${WORKDIR}/initramfs ${D}${INITFUNCTIONS_INSTALL_DIR}
}

FILES_${PN} += " \
  ${INITFUNCTIONS_INSTALL_DIR}/initramfs \
"
```

3. `recipes-core/initrdscripts/files/initramfs`  - set board specific bash
functions and variables

```
# Board specific defines
#
imx_soc="imx6ull"
keystoredev="mmc"
keystoremmcdev="1"
keystoremmcpart="boot0"
board="<board>"

# dcp blob has 138 bytes
keysize="138"

# eMMC boot0 part offset (in LBAs)
# Provide 512B to store the blob
# 4MiB - 512B (and converted to LBA)
keyoffset="8191"

keyblobpath="/tmp/dmcrypt.blob"

# We use rootfs encryption with signed key
VERIFYROOTFS="no"

CRYPT_KEY_FILE="imx6ull-<board>-rootfs-enc-key.txt"
MAC_ADDR_FILE="imx6ull-<board>-mac.txt"

# Fuse nvmem offsets for imx6ull
IMX_FUSE_SEC_CONFIG_IDX="6"
IMX_FUSE_SEC_CONFIG_VAL="\x2\x0\x0\x0"

IMX_FUSE_SRK_IDX="24"
IMX_FUSE_SRK_SIZE="8"

IMX_FUSE_MAC0_IDX="34"
IMX_FUSE_MAC1_IDX="35"

# From u-boot envs
get_root_dev_path () {
	mmc_part=$(fw_printenv -n mmcdev)
	root_part=$(fw_printenv -n rootpart)

	ROOT_DEV="/dev/mmcblk${mmc_part}p${root_part}"
}

load_key_blob () {
	tfile="/tmp/keytmp"
	dd if=/dev/mmcblk${keystoremmcdev}${keystoremmcpart} of=${tfile} bs=512 skip=${keyoffset} count=1 2>/dev/null
	dd if=${tfile} of=${keyblobpath} bs=1 count=${keysize} 2>/dev/null

	rm ${tfile}
}

imx_fuse_read () {
	idx=${1}
	count=${2}

	[ -z ${idx} ] && return 1
	[ -z ${count} ] && return 1

	ocotp_patch=$(find /sys/bus/ -name "imx-ocotp0")
	[ -z ${ocotp_patch} ] && { echo "No FUSE support!"; return 1; }
	ocotp_file=${ocotp_patch}/nvmem

	dd if=${ocotp_file} bs=4 count=${count} skip=${idx} 2>/dev/null | hexdump -e '"0x%04x\n"'
}

imx_fuse_write () {
	idx=${1}
	file=${2}

	[ -z ${idx} ] && return 1
	[ -z ${file} ] && return 1

	ocotp_patch=$(find /sys/bus/ -name "imx-ocotp0")
	[ -z ${ocotp_patch} ] && { echo "No FUSE support!"; return 1; }
	ocotp_file=${ocotp_patch}/nvmem

	# Below is a test code to check if we are going to correctly write fuses
	#hexdump ${ocotp_file}
	#dd if=/dev/zero of=/tmp/foo bs=64 count=1 2>/dev/null
	#dd if=${file} of=/tmp/foo bs=4 seek=${idx} 2>/dev/null
	#hexdump /tmp/foo

	# Real code to program fuses
	dd if=${file} of=${ocotp_file} bs=4 seek=${idx} 2>/dev/null
}

# imx6ull FUSE information
# static const char *imx6ull_otp_desc[][8] = {
# BANK8(LOCK, CFG0, CFG1, CFG2, CFG3, CFG4, CFG5, CFG6),
# BANK8(MEM0, MEM1, MEM2, MEM3, MEM4, ANA0, ANA1, ANA2),
# BANK8(OTPMK0, OTPMK1, OTPMK2, OTPMK3, OTPMK4, OTPMK5, OTPMK6, OTPMK7),
# BANK8(SRK0, SRK1, SRK2, SRK3, SRK4, SRK5, SRK6, SRK7),
# BANK8(SJC_RESP0, SJC_RESP1, MAC0, MAC1, MAC2, CRC, GP1, GP2),
# BANK8(SW_GP0, SW_GP1, SW_GP2, SW_GP3, SW_GP4,  MISC_CONF,  FIELD_RETURN, SRK_REVOKE),
# BANK8(ROM_PATCH0, ROM_PATCH1, ROM_PATCH2, ROM_PATCH3, ROM_PATCH4, ROM_PATCH5, ROM_PATCH6, ROM_PATCH7),
# BANK8(GP30, GP31, GP32, GP33, GP40, GP41, GP42, GP43),
#};

factory_get_file () {
	file="${1}"
	[ -z ${file} ] && { echo "File to get not provided!"; return 1; }

	tftp -g -r ${file} -l /tmp/${file} 192.168.0.1
}

set_ro_on_mmcboot() {
     dev="${1}"
     bootpart="${2}"
     mmcboot="mmcblk${dev}boot${bootpart}"

     [ -b /dev/${mmcboot} ] || { echo "No ${mmcboot}!"; return 1; }

     echo "Setting ${mmcboot} to RO !"
     # Please use the -p switch to set RO for boot area permanently,
     # otherwise the RO is only till next boot.
     mmc writeprotect boot set /dev/mmcblk${dev} ${bootpart}
}

# Board specific quirks:
#
board_factory_setup () {
	echo "Set booting from /dev/mmcblk1boot0"
	mmc bootpart enable 1 0 /dev/mmcblk1

	set_ro_on_mmcboot 1 0 || { echo "FAIL!"; return 1; }
	set_ro_on_mmcboot 1 1 || { echo "FAIL!"; return 1; }
}

```

The `iMX7/8` will have different offsets for eFUSE registers, but
the overall idea for read/write functions can be reused.


Links:
------

[1] - https://source.denx.de/denx/meta-mainline-common