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-$%7BCRYPTO_VERSION%7D.... -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-$%7BCRYPTO_VERSION%7D.... -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: