[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