[OpenBIOS] r609 - cpu/x86/pc/olpc
svn at openbios.org
svn at openbios.org
Tue Sep 18 01:29:27 CEST 2007
Author: wmb
Date: 2007-09-18 01:29:26 +0200 (Tue, 18 Sep 2007)
New Revision: 609
Modified:
cpu/x86/pc/olpc/crypto.bth
cpu/x86/pc/olpc/crypto.fth
cpu/x86/pc/olpc/loaddropins.fth
cpu/x86/pc/olpc/security.fth
Log:
OLPC firmware security - use 4 keys in the firmware.
Modified: cpu/x86/pc/olpc/crypto.bth
===================================================================
--- cpu/x86/pc/olpc/crypto.bth 2007-09-17 23:15:36 UTC (rev 608)
+++ cpu/x86/pc/olpc/crypto.bth 2007-09-17 23:29:26 UTC (rev 609)
@@ -5,9 +5,11 @@
fload ${BP}/cpu/x86/pc/olpc/versions.fth
-" wget http://dev.laptop.org/pub/firmware/crypto/bios_crypto-${CRYPTO_VERSION}.img -O crypto.img" expand$ $sh
-" wget http://dev.laptop.org/pub/firmware/crypto/testkeys/os.public -O os.public" expand$ $sh
-" wget http://dev.laptop.org/pub/firmware/crypto/testkeys/fw.public -O fw.public" expand$ $sh
+" wget http://dev.laptop.org/pub/firmware/crypto/bios_verify-${CRYPTO_VERSION}.img -O verify.img" expand$ $sh
+" wget http://dev.laptop.org/pub/firmware/crypto/testkeys/os.public -O os.public" expand$ $sh
+" wget http://dev.laptop.org/pub/firmware/crypto/testkeys/fw.public -O fw.public" expand$ $sh
+" wget http://dev.laptop.org/pub/firmware/crypto/testkeys/os.public -O lease.public" expand$ $sh
+" wget http://dev.laptop.org/pub/firmware/crypto/testkeys/fw.public -O developer.public" expand$ $sh
\ This forces the creation of an crypto.log file, so we don't re-fetch
writing crypto.version
Modified: cpu/x86/pc/olpc/crypto.fth
===================================================================
--- cpu/x86/pc/olpc/crypto.fth 2007-09-17 23:15:36 UTC (rev 608)
+++ cpu/x86/pc/olpc/crypto.fth 2007-09-17 23:29:26 UTC (rev 609)
@@ -1,9 +1,9 @@
purpose: Interface to cryptographic code for firmware image validation
\ See license at end of file
-h# c0000 constant crypto-base \ The address the code is linked to run at
-h# d0000 constant crypto-bss \ The address the code is linked to run at
-h# 10000 constant /crypto-bss
+h# c0000 constant verify-base \ The address the code is linked to run at
+h# d0000 constant verify-bss \ The address the code is linked to run at
+h# 10000 constant /verify-bss
0 [if]
h# c0000 constant hasher-base \ The address the code is linked to run at
@@ -25,34 +25,17 @@
0 value crypto-loaded?
: load-crypto ( -- error? )
crypto-loaded? if false exit then
- " crypto" find-drop-in 0= if true exit then ( prog$ )
- 2dup crypto-base swap move free-mem ( )
+ " verify" find-drop-in 0= if true exit then ( prog$ )
+ 2dup verify-base swap move free-mem ( )
true to crypto-loaded?
false
;
-h# 200 buffer: pubkey
-0 value /pubkey
-: load-key ( name$ -- error? )
- find-drop-in 0= if true exit then ( key$ )
- dup h# 200 > if free-mem true exit then ( key$ )
- dup to /pubkey ( key$ )
- 2dup pubkey swap move ( key$ )
- free-mem
- false
-;
+: signature-bad? ( data$ sig$ key$ hashname$ -- mismatch? )
+ $cstr
+ verify-bss /verify-bss erase ( data$ sig$ key$ 'hashname )
+ verify-base dup h# 10 - sp-call >r 3drop 4drop r> ( result )
-: signature-bad? ( data$ sig$ hashname$ -- mismatch? )
- $cstr >r ( data$ sig$ r: 'hashname )
-
- swap 2swap swap ( siglen sigadr datalen dataadr )
- /pubkey pubkey 2swap ( siglen sigadr keylen keyadr datalen dataadr )
- r> ( siglen sigadr keylen keyadr datalen dataadr 'hashname )
-
-
- crypto-bss /crypto-bss erase
- crypto-base dup h# 10 - sp-call >r 3drop 4drop r> ( result )
-
\ XXX free-mem in suspend.fth and fw.bth after find-drop-in
\ XXX clean out dead code in usb.fth
;
Modified: cpu/x86/pc/olpc/loaddropins.fth
===================================================================
--- cpu/x86/pc/olpc/loaddropins.fth 2007-09-17 23:15:36 UTC (rev 608)
+++ cpu/x86/pc/olpc/loaddropins.fth 2007-09-17 23:29:26 UTC (rev 609)
@@ -38,6 +38,8 @@
" ${BP}/ofw/termemu/gallant.obf" " font" $add-deflated-dropin
- " crypto.img" " crypto" $add-deflated-dropin
- " os.public" " oskey" $add-dropin \ Incompressible
- " fw.public" " fwkey" $add-dropin \ Incompressible
+ " verify.img" " verify" $add-deflated-dropin
+ " os.public" " ospubkey" $add-dropin \ Incompressible
+ " fw.public" " fwpubkey" $add-dropin \ Incompressible
+ " lease.public" " leasepubkey" $add-dropin \ Incompressible
+ " developer.public" " develpubkey" $add-dropin \ Incompressible
Modified: cpu/x86/pc/olpc/security.fth
===================================================================
--- cpu/x86/pc/olpc/security.fth 2007-09-17 23:15:36 UTC (rev 608)
+++ cpu/x86/pc/olpc/security.fth 2007-09-17 23:29:26 UTC (rev 609)
@@ -3,6 +3,7 @@
\ Specs at http://wiki.laptop.org/go/Firmware_Security
+: developer-device-list " disk sd nand" ;
: boot-device-list " disk sd nand" ;
true value debug-security?
@@ -34,9 +35,44 @@
: PN pn-buf count ;
previous definitions
+\ key: is a defining word whose children return key strings.
+\ Each child word has the name of its key stored in the dictionary.
+\ The first time that a child word executes, it uses the key name
+\ to find the key value and caches the key value in RAM so subsequent
+\ uses are faster.
+
+: key: ( name$ "name" -- key$ )
+ create 0 , 0 , ", \ adr len name
+ does> ( apf -- key$ )
+ dup @ if 2@ exit then ( apf )
+ dup 2 na+ count ( apf name$ )
+ 2dup find-drop-in if ( apf name$ key$ )
+ 2nip
+ else ( apf name$ )
+ ." Can't load key " type cr
+ " Missing Key" ( apf bad-key$ )
+ then
+ rot >r 2dup r> 2! ( key$ )
+;
+" ospubkey" key: oskey$
+" fwpubkey" key: fwkey$
+" develpubkey" key: develkey$
+" leasepubkey" key: leasekey$
+
+\ pubkey$ is a global variable that points to the currently-selected
+\ public key string. It simplifies the stack manipulations for other
+\ words, since the same key string is often used multiple times.
+0 0 2value pubkey$
+
+\ sig-buf is used for storing the binary version of signature strings
+\ that have been decoded from the hex representation.
+
d# 256 constant /sig
/sig buffer: sig-buf
+\ hex-decode decodes a hexadecimal signature string, storing it in
+\ binary form at sig-buf. It returns the adr,len of the binary string.
+
: hex-decode ( hex$ -- true | sig$ false )
dup /sig 2* <> if
( ." Bad signature length" cr )
@@ -52,6 +88,10 @@
sig-buf tuck - false ( sig$ false )
;
+\ parse-sig parses a "sig01:" format signature string, returning its
+\ hashname and signature substrings. It converts the signature
+\ substring from ASCII hex to binary bytes.
+
: parse-sig ( sig01$ -- true | hashname$ sig$ false )
dup d# 89 < if 2drop true exit then
bl left-parse-string " sig01:" $= 0= if 2drop true exit then ( rem$ )
@@ -61,6 +101,12 @@
hex-decode if 2drop true else false then
;
+\ zip-extent looks inside a memory-resident ZIP archive and returns
+\ the address,length of a given component of that archive. This
+\ assumes that the components are "stored", not "deflated". It
+\ depends on the existence of a support package named "/lzip" to
+\ do the work.
+
: zip-extent ( name$ -- adr len )
expand$ open-dev ?dup 0= if " " exit then
>r
@@ -68,10 +114,23 @@
" size" r@ $call-method drop
r> close-dev
;
+
+\ sig$ and img$ find the signature and signed-image components of
+\ a ZIP bundle image that is already in memory.
+
: sig$ ( -- adr len ) " /lzip:\data.sig" zip-extent ;
: img$ ( -- adr len ) " /lzip:\data.img" zip-extent ;
+
+\ bundle-name$ returns the full OFW pathname of a signed image
+\ bundle, piecing it together from the device (DN), path (PN),
+\ filename head (CN), and filename body (FN) macros.
+
: bundle-name$ ( -- $ ) " ${DN}:${PN}\${CN}${FN}.zip" expand$ ;
+\ bundle-present? determines the existence (or not) of a signed image
+\ bundle whose name is constructed from the current settings of the
+\ device (DN), path (PN), filename head (CN), and filename body (FN).
+
: bundle-present? ( -- flag )
bundle-name$
" Trying " ?lease-debug 2dup ?lease-debug-cr
@@ -79,32 +138,53 @@
true
;
+\ hashname remembers the most recently used hashname to guard against
+\ attacks based on reuse of the same (presumably compromized) hash.
+
d# 32 buffer: hashname
-\ fn-buf and pn-buf must contain the base file name and path
-: valid? ( data$ sig$ -- okay? )
+
+\ valid? checks the validity of data$ against the ASCII signature
+\ record sig01$, using the public key that pubkey$ points to.
+\ It also verifies that the hashname contained in sig01$ is not
+\ the same one that was last used (for verification of firmware
+\ images against two different hashes).
+
+: valid? ( data$ sig01$ -- okay? )
parse-sig if
." Bad signature format in " bundle-name$ type cr
false exit
then ( data$ hashname$ sig$ )
+ 2swap d# 31 min ( data$ sig$ hashname$' )
+
\ Check for duplicate hashname attacks
- 2swap 2dup hashname count $= if ( data$ sig$ hashname$ )
+ 2dup hashname count $= if ( data$ sig$ hashname$ )
." Duplicate hash name in " bundle-name$ type cr
4drop false exit
- then
+ then ( data$ sig$ hashname$ )
- d# 31 min hashname place ( data$ sig$ )
+ hashname place ( data$ sig$ )
- hashname count signature-bad? 0=
+ pubkey$ hashname count signature-bad? 0= ( okay? )
;
+\ earliest is the earliest acceptable date value (in seconds).
+\ It is the date that the first test version of this code was
+\ deployed. If a laptop has any earlier date that than, that
+\ date is presumed bogus.
+
d# 2007 d# 12 * 8 1- + d# 31 * d# 27 + constant earliest
+
0. 2value current-seconds
-\ This isn't an accurate calculation of seconds, but it
-\ is sufficient for comparison purposes so long as we
-\ use the same calculation in all cases. It is not good
-\ if we need to do arithmetic on dates.
+\ get-date reads the date and time from the real time clock
+\ and converts it to seconds.
+
+\ The seconds conversion uses a simplified approach that ignores
+\ leap years and the like - it assumes that all months are 31 days.
+\ This is sufficient for comparison purposes so long as we use the
+\ same calculation in all cases. It is not good for doing
+\ arithmetic on dates.
: get-date ( -- error? )
time&date ( s m h d m y )
d# 12 * swap 1- + ( s m h d m' ) \ Months start at 1
@@ -121,17 +201,24 @@
false
;
+\ break$ splits a string into an initial substring of length n
+\ (head$) and the residual substring (tail$). If the input
+\ string is shorter than n, head$ is the input string and tail$ is
+\ the null string.
+
: break$ ( $ n -- tail$ head$ )
+ 2dup < if drop null$ 2swap exit then
dup >r /string ( tail$ )
over r@ - r> ( tail$ head$ )
;
0. 2value exp-seconds \ Accumulator for parsing data/time strings
-\ This is a factor used for parsing 2-digit fields from date/time strings.
+\ numfield is a factor used for parsing 2-digit fields from date/time strings.
\ Radix is the number to scale the result by, i.e. one more than the maximum
\ value of the field. Adjust is 0 for fields whose first valid value is 0
\ (hours, minutes, seconds) or 1 for fields that start at 1 (month,day).
+
: numfield ( exp$ adjust radix -- exp$' )
>r >r ( exp$ r: radix adjust )
2 break$ $number throw ( exp$' num r: radix adjust )
@@ -144,6 +231,10 @@
rot 0 d+ to exp-seconds ( exp$ )
;
+\ expiration-to-seconds parses an expiration date string like
+\ "20070820T130401Z", converting it to (double precision) seconds
+\ according to the simplified calculation described above for "get-date"
+
: (expiration-to-seconds) ( expiration$ -- true | d.seconds false )
4 break$ $number throw ( exp$' year )
dup d# 2999 u> throw ( exp$' year )
@@ -169,6 +260,9 @@
dup if nip nip then
;
+\ expired? determines whether or not the expiration time string is
+\ earlier than this machine's current time (from the real time clock).
+
: expired? ( expiration$ -- bad? )
expiration-to-seconds if true exit then
current-seconds d<
@@ -177,15 +271,24 @@
d# 1024 constant /sec-line-max
/sec-line-max buffer: sec-line-buf
-\ Remove bogus null characters from the end of tags on old machines
+\ Remove bogus null characters from the end of mfg data tags (old machines
+\ have malformed tags)
: ?-null ( adr len -- adr' len' )
dup if
2dup + 1- c@ 0= if 1- then ( adr len' )
then
;
+\ machine-id-buf is a buffer into which the machine signature string,
+\ including serial number, UUID, and expiration time, is place.
+\ That string is the signed object for lease and developer key verification.
+
d# 65 buffer: machine-id-buf
+\ get-my-sn get the machine identification info including serial number
+\ and UUID from the manufacturing data, placing it into machine-id-buf
+\ for later use. The expiration time is added later.
+
: get-my-sn ( -- error? )
" SN" find-tag 0= if
@@ -214,18 +317,33 @@
false
;
+
+\ my-sn$ returns the serial number portion of the machine identification.
+\ get-my-sn must be called before my-sn$ will be valid.
+
: my-sn$ ( -- adr len ) machine-id-buf d# 11 ;
+
+\ check-machine-signature verifies the signed object consisting
+\ of the machine identification info (SN + UUID) plus the expiration
+\ time "expiration$" against the crypto signature string sig$,
+\ returning 1 if valid, -1 if invalid. (The unusual return value
+\ encoding is because the caller of check-machine-signature returns
+\ a tree-state flag; see check-lease.)
+
: check-machine-signature ( sig$ expiration$ -- -1|1 )
0 hashname c!
machine-id-buf d# 49 + swap move ( sig$ )
machine-id-buf d# 65 2swap valid? if 1 else -1 then
;
+\ check-lease checks a lease signature record in act01: format
+
\ -1 means lease is for this machine and is invalid
\ 1 means lease is for this machine and is valid
\ 0 means lease is not for this machine
-: check-lease ( lease$ -- -1|0|1 )
+
+: check-lease ( act01-lease$ -- -1|0|1 )
bl left-parse-string " act01:" $= 0= if
" Not act01:" ?lease-debug-cr
2drop -1 exit
@@ -253,12 +371,18 @@
then
;
-: lease-valid? ( -- flag )
+\ lease-valid? tries to read a lease file from the currently-selected
+\ device, searches it for a lease record corresponding to this machine,
+\ and checks that record for validity. The return value is true if
+\ a valid lease was found.
+
+: lease-valid? ( -- valid? )
" ${DN}:\security\lease.sig" expand$ ( name$ )
" Trying " ?lease-debug 2dup ?lease-debug-cr
r/o open-file if drop false exit then ( ih )
>r ( r: ih )
" Lease " ?lease-debug ( r: ih )
+ leasekey$ to pubkey$ ( r: ih )
begin
sec-line-buf /sec-line-max r@ read-line if ( actual -eof? )
2drop r> close-file drop false exit
@@ -272,11 +396,25 @@
r> close-file drop false
;
+\ ?leased checks the currently-selected device for a valid lease
+\ (see lease-valid?), setting the CN macro to "run" if one was
+\ found or to "act" otherwise. CN is used to construct a filename
+\ like "runos.zip" (the normal OS, used when an valid lease is
+\ present) or "actos.zip" (the activation version of the OS).
+
: ?leased ( -- )
lease-valid? if " run" else " act" then cn-buf place
;
-: olpc-load-image ( list$ pathname$ -- okay? )
+\ olpc-load-image is factor that is close the top level of the
+\ secure boot process. Given a directory prefix (e.g. "\boot")
+\ and a space-delimited list of device names, it searches
+\ each device in that list for an OS bundle in that directory.
+\ The name of the OS bundle file is either "actos.zip" or
+\ "runos.zip" according to whether or not a valid lease for
+\ this machine is present on the same device.
+
+: olpc-load-image ( list$ dirname$ -- okay? )
pn-buf place ( list$ )
begin dup while ( list$ )
bl left-parse-string ( list$ devname$ )
@@ -285,6 +423,7 @@
bundle-present? if ( list$ )
" OS found - " ?lease-debug
0 hashname c!
+ oskey$ to pubkey$
img$ sig$ valid? if
" Signature valid" ?lease-debug-cr
img$ tuck load-base swap move !load-size
@@ -297,20 +436,20 @@
2drop false
;
+\ secure-load is the top level of the secure OS loading process.
+\ It searches for lease files and signed OS image bundles on several
+\ different devices. If an OS bundle is not found, it then searches
+\ the NAND FLASH for an alternate OS image.
+
: secure-load ( -- okay? )
load-crypto if ( )
- ." Can't get crypt code" cr ( )
+ ." Can't get crypto code" cr ( )
false exit
then ( )
get-my-sn if false exit then
get-date if false exit then
- " oskey" load-key if ( )
- ." Can't find OS public key" cr ( )
- false exit
- then ( )
-
" os" fn-buf place
boot-device-list " \boot" olpc-load-image if true exit then
@@ -318,8 +457,19 @@
false
;
+\ secure-load-ramdisk is called during the process of preparing an
+\ OS image for execution. It looks for an initrd bundle file on
+\ the same device where the OS image was found, in a file named
+\ either "runrd.zip" or "actrd.zip" depending on the presence of
+\ a valid lease.
+
+\ If no such bundle is found, the OS is booted without a ramdisk.
+\ If a valid bundle is found, the OS is booted with that ramdisk.
+\ If a bundle is found but it is not valid, the booting process aborts.
+
\ Call this after the kernel has already been moved away from load-base
\ We assume that pn-buf already has the path prefix string
+
: secure-load-ramdisk ( -- )
\ Bad idea, because the cmdline would need to be signed too
\ " /lzip:\cmdline" zip-extent to cmdline
@@ -338,14 +488,47 @@
then
;
-: check-devel-key ( adr len -- -1|0|1 )
+
+\ secure-boot performs the secure boot process
+
+: secure-boot ( -- )
+ debug-security? if screen-ih stdout ! then
+ ['] secure-load-ramdisk to load-ramdisk
+ secure-load 0= if fail-load then
+ loaded sync-cache " init-program" $find if execute else 2drop then
+ go
+;
+
+\ wp? returns true if a "wp" manufacturing data tag is present
+
+: wp? ( -- flag ) " wp" find-tag dup if nip nip then ;
+
+\ ?secure-boot performs either the secure boot algorithm or the
+\ historical boot algorithm depending on the presence of a "wp"
+\ manufacturing data tag.
+
+: ?secure-boot ( -- ) wp? if secure-boot else boot then ;
+" ?secure-boot" ' boot-command set-config-string-default
+
+
+\ check-devel-key tests the developer signature string "dev01$".
+
+\ -1 means the signature is for this machine and is invalid
+\ 1 means the signature is for this machine and is valid
+\ 0 means the signature is not for this machine
+
+: check-devel-key ( dev01$ -- -1|0|1 )
bl left-parse-string " dev01:" $= 0= if 2drop -1 exit then ( rem$ )
bl left-parse-string ( rem$ serial$ )
- my-sn$ $= 0= if 2drop 0 exit then ( rem$ )
+ my-sn$ $= 0= if 2drop 0 exit then ( rem$ )
+ develkey$ to pubkey$
" 00000000T000000Z" check-machine-signature
;
+\ has-developer-key? searches for a valid developer key on the
+\ device given by the DN macro.
+
: has-developer-key? ( -- flag )
" ${DN}:\security\develop.sig" expand$ ( name$ )
r/o open-file if drop false exit then ( ih )
@@ -363,22 +546,18 @@
r> close-file drop false
;
-: developer-device-list " disk sd nand" ;
+\ developer? searches a list of devices (given by "developer-device-list")
+\ for a valid developer key
: developer? ( -- flag )
- get-my-sn if false exit then
+ get-my-sn if false exit then
load-crypto if ( )
." Can't get crypt code" cr ( )
false exit
then ( )
- " fwkey" load-key if ( )
- ." Can't find firmware public key" cr ( )
- false exit
- then ( )
-
- developer-device-list
+ developer-device-list ( list$ )
begin dup while ( list$ )
bl left-parse-string dn-buf place ( list$' )
has-developer-key? if ( list$' )
@@ -388,22 +567,7 @@
2drop false
;
-: secure-boot ( -- )
- debug-security? if screen-ih stdout ! then
- ['] secure-load-ramdisk to load-ramdisk
- secure-load 0= if fail-load then
- loaded sync-cache " init-program" $find if execute else 2drop then
- go
-;
-: wp? ( -- flag ) " wp" find-tag dup if nip nip then ;
-
-: ?secure-boot ( -- ) wp? if secure-boot else boot then ;
-" ?secure-boot" ' boot-command set-config-string-default
-
-\ For dn in boot-device-list
-\ if
-
fexit
Firmware security use cases:
More information about the OpenBIOS
mailing list