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 }