github.com/transparency-dev/armored-witness-os@v0.1.3-0.20240514084412-27eef7325168/trusted_os/rpmb.go (about)

     1  // Copyright 2022 The Armored Witness OS authors. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build !fake_rpmb
    16  // +build !fake_rpmb
    17  
    18  package main
    19  
    20  import (
    21  	"bytes"
    22  	"crypto/aes"
    23  	"crypto/sha256"
    24  	"encoding/gob"
    25  	"errors"
    26  	"fmt"
    27  	"io"
    28  	"log"
    29  
    30  	"golang.org/x/crypto/pbkdf2"
    31  
    32  	"github.com/usbarmory/tamago/soc/nxp/imx6ul"
    33  	"github.com/usbarmory/tamago/soc/nxp/usdhc"
    34  
    35  	"github.com/coreos/go-semver/semver"
    36  	"github.com/usbarmory/crucible/otp"
    37  
    38  	"github.com/transparency-dev/armored-witness-os/rpmb"
    39  )
    40  
    41  const (
    42  	// RPMB sector for CVE-2020-13799 mitigation
    43  	dummySector = 0
    44  	// version epoch length
    45  	versionLength = 32
    46  	// RPMB sector for OS rollback protection
    47  	osVersionSector = 1
    48  	// RPMB sector for TA rollback protection
    49  	taVersionSector = 2
    50  	// RPMB sector for TA use
    51  	taUserSector = 3
    52  	// RPMB OTP flag bank
    53  	rpmbFuseBank = 4
    54  	// RPMB OTP flag word
    55  	rpmbFuseWord = 6
    56  
    57  	diversifierMAC = "ArmoryWitnessMAC"
    58  	iter           = 4096
    59  )
    60  
    61  type RPMB struct {
    62  	storage   Card
    63  	partition *rpmb.RPMB
    64  }
    65  
    66  func newRPMB(storage Card) (r *RPMB, err error) {
    67  	return &RPMB{storage: storage}, nil
    68  }
    69  
    70  // isProgrammed returns true if the RPMB key program key flag is set to 1.
    71  func (p *RPMB) isProgrammed() (bool, error) {
    72  	res, err := otp.ReadOCOTP(rpmbFuseBank, rpmbFuseWord, 0, 1)
    73  	if err != nil {
    74  		return false, fmt.Errorf("could not read RPMB program key flag (%x, %v)", res, err)
    75  	}
    76  	return bytes.Equal(res, []byte{1}), nil
    77  }
    78  
    79  func (r *RPMB) init() error {
    80  	// derived key for RPBM MAC generation
    81  	var dk []byte
    82  	var err error
    83  
    84  	switch {
    85  	case imx6ul.CAAM != nil:
    86  		dk = make([]byte, sha256.Size) // dk needs to be correctly sized to receive the key.
    87  		err = imx6ul.CAAM.DeriveKey([]byte(diversifierMAC), dk)
    88  	case imx6ul.DCP != nil:
    89  		dk, err = imx6ul.DCP.DeriveKey([]byte(diversifierMAC), make([]byte, aes.BlockSize), -1)
    90  	default:
    91  		err = errors.New("unsupported hardware")
    92  	}
    93  	if err != nil {
    94  		return fmt.Errorf("could not derive RPMB key (%v)", err)
    95  	}
    96  
    97  	uid := imx6ul.UniqueID()
    98  
    99  	card, ok := r.storage.(*usdhc.USDHC)
   100  	if !ok {
   101  		return errors.New("could not assert type *usdhc.USDHC from Card")
   102  	}
   103  
   104  	isProgrammed, err := r.isProgrammed()
   105  	if err != nil {
   106  		return err
   107  	}
   108  	// setup RPMB
   109  	r.partition, err = rpmb.Init(
   110  		card,
   111  		pbkdf2.Key(dk, uid[:], iter, sha256.Size, sha256.New),
   112  		dummySector,
   113  		isProgrammed,
   114  	)
   115  	if err != nil {
   116  		return fmt.Errorf("RPMB could not be initialized: %v", err)
   117  	}
   118  
   119  	_, err = r.partition.Counter(false)
   120  	if err != nil {
   121  		var e *rpmb.OperationError
   122  		if !errors.As(err, &e) {
   123  			return fmt.Errorf("RPMB failed to read counter: %v", err)
   124  		}
   125  		if e.Result != rpmb.AuthenticationKeyNotYetProgrammed {
   126  			return fmt.Errorf("RPMB failed to read counter with operatation error: %v", err)
   127  		}
   128  	}
   129  
   130  	// Fuse a bit to indicate previous key programming to prevent malicious
   131  	// eMMC replacement to intercept ProgramKey().
   132  	//
   133  	// If already fused refuse to do any programming and bail.
   134  	if isProgrammed {
   135  		log.Printf("RPMB program key flag already fused")
   136  		return nil
   137  	}
   138  
   139  	if err = otp.BlowOCOTP(rpmbFuseBank, rpmbFuseWord, 0, 1, []byte{1}); err != nil {
   140  		return fmt.Errorf("could not fuse RPMB program key flag (%v)", err)
   141  	}
   142  
   143  	log.Print("RPMB authentication key not yet programmed, programming")
   144  
   145  	if err = r.partition.ProgramKey(); err != nil {
   146  		return fmt.Errorf("could not program RPMB key")
   147  	}
   148  
   149  	return nil
   150  }
   151  
   152  func parseVersion(s string) (version *semver.Version, err error) {
   153  	return semver.NewVersion(s)
   154  }
   155  
   156  // expectedVersion returns the version epoch stored in an RPMB area of the
   157  // internal eMMC.
   158  func (r *RPMB) expectedVersion(offset uint16) (*semver.Version, error) {
   159  	if r.partition == nil {
   160  		return nil, errors.New("RPMB has not been initialized")
   161  	}
   162  
   163  	buf := make([]byte, versionLength)
   164  	if err := r.partition.Read(offset, buf); err != nil {
   165  		return nil, err
   166  	}
   167  	var v string
   168  	if err := gob.NewDecoder(bytes.NewBuffer(buf)).Decode(&v); err != nil {
   169  		if err == io.EOF {
   170  			// We've not previously stored a version, so return 0.0.0
   171  			return semver.NewVersion("0.0.0")
   172  		}
   173  		return nil, err
   174  	}
   175  
   176  	return semver.NewVersion(v)
   177  }
   178  
   179  // updateVersion writes a new version epoch in an RPMB area of the internal
   180  // eMMC.
   181  func (r *RPMB) updateVersion(offset uint16, version semver.Version) error {
   182  	if r.partition == nil {
   183  		return errors.New("RPMB has not been initialized")
   184  	}
   185  	buf := &bytes.Buffer{}
   186  	if err := gob.NewEncoder(buf).Encode(version.String()); err != nil {
   187  		return err
   188  	}
   189  	return r.partition.Write(offset, buf.Bytes())
   190  }
   191  
   192  // checkVersion verifies version information against RPMB stored data.
   193  //
   194  // If the passed version is older than the RPMB area information of the
   195  // internal eMMC an error is returned.
   196  //
   197  // If the passed version is more recent than the RPMB area information then the
   198  // internal eMMC is updated with it.
   199  func (r *RPMB) checkVersion(offset uint16, s string) error {
   200  	runningVersion, err := parseVersion(s)
   201  	if err != nil {
   202  		return err
   203  	}
   204  
   205  	expectedVersion, err := r.expectedVersion(offset)
   206  	if err != nil {
   207  		return err
   208  	}
   209  
   210  	switch {
   211  	case runningVersion.LessThan(*expectedVersion):
   212  		return errors.New("version mismatch")
   213  	case expectedVersion.Equal(*runningVersion):
   214  		return nil
   215  	case expectedVersion.LessThan(*runningVersion):
   216  		return r.updateVersion(offset, *runningVersion)
   217  	}
   218  
   219  	return nil
   220  }
   221  
   222  // transfer performs an authenticated data transfer to the card RPMB partition,
   223  // the input buffer can contain up to 256 bytes of data, n can be passed to
   224  // retrieve the partition write counter.
   225  func (r *RPMB) transfer(offset uint16, buf []byte, n *uint32, write bool) (err error) {
   226  	if write {
   227  		err = r.partition.Write(offset, buf)
   228  	} else {
   229  		err = r.partition.Read(offset, buf)
   230  	}
   231  
   232  	if err != nil && n != nil {
   233  		*n, err = r.partition.Counter(true)
   234  	}
   235  
   236  	return
   237  }