1 /* 2 * File: EncoreBootImageReader.cpp 3 * 4 * Copyright (c) Freescale Semiconductor, Inc. All rights reserved. 5 * See included license file for license details. 6 */ 7 8 #include "EncoreBootImageReader.h" 9 #include "SHA1.h" 10 #include "rijndael.h" 11 #include "RijndaelCBCMAC.h" 12 #include <assert.h> 13 #include "EndianUtilities.h" 14 #include "Logging.h" 15 16 using namespace elftosb; 17 18 //! \post Stream head points to just after the image header. 19 //! \exception read_error Thrown if the image header is invalid. 20 void EncoreBootImageReader::readImageHeader() 21 { 22 // seek to beginning of the stream/file and read the plaintext header 23 m_stream.seekg(0, std::ios_base::beg); 24 if (m_stream.read((char *)&m_header, sizeof(m_header)).bad()) 25 { 26 throw read_error("failed to read image header"); 27 } 28 29 m_header.m_flags = ENDIAN_LITTLE_TO_HOST_U16(m_header.m_flags); 30 m_header.m_imageBlocks = ENDIAN_LITTLE_TO_HOST_U32(m_header.m_imageBlocks); 31 m_header.m_firstBootTagBlock = ENDIAN_LITTLE_TO_HOST_U32(m_header.m_firstBootTagBlock); 32 m_header.m_firstBootableSectionID = ENDIAN_LITTLE_TO_HOST_U32(m_header.m_firstBootableSectionID); 33 m_header.m_keyCount = ENDIAN_LITTLE_TO_HOST_U16(m_header.m_keyCount); 34 m_header.m_keyDictionaryBlock = ENDIAN_LITTLE_TO_HOST_U16(m_header.m_keyDictionaryBlock); 35 m_header.m_headerBlocks = ENDIAN_LITTLE_TO_HOST_U16(m_header.m_headerBlocks); 36 m_header.m_sectionCount = ENDIAN_LITTLE_TO_HOST_U16(m_header.m_sectionCount); 37 m_header.m_sectionHeaderSize = ENDIAN_LITTLE_TO_HOST_U16(m_header.m_sectionHeaderSize); 38 m_header.m_timestamp = ENDIAN_LITTLE_TO_HOST_U64(m_header.m_timestamp); 39 40 // m_header.m_componentVersion.m_major = ENDIAN_BIG_TO_HOST_U16(m_header.m_componentVersion.m_major); 41 // m_header.m_componentVersion.m_minor = ENDIAN_BIG_TO_HOST_U16(m_header.m_componentVersion.m_minor); 42 // m_header.m_componentVersion.m_revision = ENDIAN_BIG_TO_HOST_U16(m_header.m_componentVersion.m_revision); 43 44 // m_header.m_productVersion.m_major = ENDIAN_BIG_TO_HOST_U16(m_header.m_productVersion.m_major); 45 // m_header.m_productVersion.m_minor = ENDIAN_BIG_TO_HOST_U16(m_header.m_productVersion.m_minor); 46 // m_header.m_productVersion.m_revision = ENDIAN_BIG_TO_HOST_U16(m_header.m_productVersion.m_revision); 47 48 // check header signature 1 49 if (m_header.m_signature[0] != 'S' || m_header.m_signature[1] != 'T' || m_header.m_signature[2] != 'M' || m_header.m_signature[3] != 'P') 50 { 51 throw read_error("invalid signature 1"); 52 } 53 54 // check header signature 2 for version 1.1 and greater 55 if ((m_header.m_majorVersion > 1 || (m_header.m_majorVersion == 1 && m_header.m_minorVersion >= 1)) && (m_header.m_signature2[0] != 's' || m_header.m_signature2[1] != 'g' || m_header.m_signature2[2] != 't' || m_header.m_signature2[3] != 'l')) 56 { 57 // throw read_error("invalid signature 2"); 58 Log::log(Logger::WARNING, "warning: invalid signature 2\n"); 59 } 60 } 61 62 //! \pre The image header must have already been read with a call to readImageHeader(). 63 //! 64 void EncoreBootImageReader::computeHeaderDigest(sha1_digest_t & digest) 65 { 66 CSHA1 hash; 67 hash.Reset(); 68 hash.Update((uint8_t *)&m_header.m_signature, sizeof(m_header) - sizeof(sha1_digest_t)); 69 hash.Final(); 70 hash.GetHash(digest); 71 } 72 73 //! \pre The image header must have already been read. 74 //! \pre The DEK must have been found already. 75 //! \post The stream head is at the end of the digest. 76 void EncoreBootImageReader::readImageDigest() 77 { 78 unsigned digestPosition = sizeOfCipherBlocks(m_header.m_imageBlocks - 2); 79 m_stream.seekg(digestPosition, std::ios_base::beg); 80 81 // read the two cipher blocks containing the digest, including padding 82 cipher_block_t digestBlocks[2]; 83 if (m_stream.read((char *)&digestBlocks, sizeof(digestBlocks)).bad()) 84 { 85 throw read_error("failed to read image digest"); 86 } 87 88 // decrypt the digest 89 if (isEncrypted()) 90 { 91 Rijndael cipher; 92 cipher.init(Rijndael::CBC, Rijndael::Decrypt, m_dek, Rijndael::Key16Bytes, m_header.m_iv); 93 cipher.blockDecrypt((uint8_t *)&digestBlocks, sizeof(digestBlocks) * 8, (uint8_t *)&digestBlocks); 94 } 95 96 // copy the digest out of the padded blocks 97 memcpy(m_digest, &digestBlocks, sizeof(m_digest)); 98 } 99 100 //! \pre The image header must have already been read with a call to readImageHeader(). 101 //! \post The stream head is at the end of the image minus the last two cipher blocks. 102 //! \param digest Where to store the resulting digest. 103 //! \exception read_error Thrown if the image header is invalid. 104 void EncoreBootImageReader::computeImageDigest(sha1_digest_t & digest) 105 { 106 m_stream.seekg(0, std::ios_base::beg); 107 108 CSHA1 hash; 109 hash.Reset(); 110 111 unsigned blockCount = m_header.m_imageBlocks - 2; // exclude digest at end of image 112 while (blockCount--) 113 { 114 cipher_block_t block; 115 if (m_stream.read((char *)&block, sizeof(block)).bad()) 116 { 117 throw read_error("failed to read block while computing image digest"); 118 } 119 hash.Update(block, sizeof(block)); 120 } 121 122 hash.Final(); 123 hash.GetHash(digest); 124 } 125 126 //! \pre Image header must have been read before this method is called. 127 //! 128 void EncoreBootImageReader::readSectionTable() 129 { 130 // seek to the table 131 m_stream.seekg(sizeOfCipherBlocks(m_header.m_headerBlocks), std::ios_base::beg); 132 133 unsigned sectionCount = m_header.m_sectionCount; 134 while (sectionCount--) 135 { 136 EncoreBootImage::section_header_t header; 137 if (m_stream.read((char *)&header, sizeof(header)).bad()) 138 { 139 throw read_error("failed to read section header"); 140 } 141 142 // swizzle section header 143 header.m_tag = ENDIAN_LITTLE_TO_HOST_U32(header.m_tag); 144 header.m_offset = ENDIAN_LITTLE_TO_HOST_U32(header.m_offset); 145 header.m_length = ENDIAN_LITTLE_TO_HOST_U32(header.m_length); 146 header.m_flags = ENDIAN_LITTLE_TO_HOST_U32(header.m_flags); 147 148 m_sections.push_back(header); 149 } 150 } 151 152 //! Requires that an OTP key has been provided as the sole argument. Passing the 153 //! key into this method lets the caller search the key dictionary for any number 154 //! of keys and determine which are valid. If \a kek is found in the dictionary, 155 //! the decrypted DEK is saved and true is returned. A result of false means 156 //! that \a kek was not found. 157 //! 158 //! \pre The image header and section table must have been read already. 159 //! \post The stream head points somewhere inside the key dictionary, or just after it. 160 //! \post If the search was successful, the #m_dek member will contain the decrypted 161 //! session key. Otherwise #m_dek is not modified. 162 //! \param kek Search for this KEK in the dictionary. 163 //! \retval true The DEK was found and decrypted. True is also returned when the 164 //! image is not encrypted at all. 165 //! \retval false No matching key entry was found. The image cannot be decrypted. 166 bool EncoreBootImageReader::readKeyDictionary(const AESKey<128> & kek) 167 { 168 // do nothing if the image is not encrypted 169 if (!isEncrypted()) 170 { 171 return true; 172 } 173 174 // first compute a CBC-MAC over the image header with our KEK 175 RijndaelCBCMAC mac(kek); 176 mac.update((const uint8_t *)&m_header, sizeof(m_header)); 177 178 // run the CBC-MAC over each entry in the section table too 179 section_array_t::iterator it = m_sections.begin(); 180 for (; it != m_sections.end(); ++it) 181 { 182 mac.update((const uint8_t *)&(*it), sizeof(EncoreBootImage::section_header_t)); 183 } 184 185 // get the CBC-MAC result 186 mac.finalize(); 187 const RijndaelCBCMAC::block_t & macResult = mac.getMAC(); 188 189 // seek to the key dictionary 190 m_stream.seekg(sizeOfCipherBlocks(m_header.m_keyDictionaryBlock), std::ios_base::beg); 191 192 // decipher each key entry 193 unsigned entries = m_header.m_keyCount; 194 while (entries--) 195 { 196 // read the entry 197 EncoreBootImage::dek_dictionary_entry_t entry; 198 if (m_stream.read((char *)&entry, sizeof(entry)).bad()) 199 { 200 throw read_error("failed to read key dictionary entry"); 201 } 202 203 // compare the CBC-MAC we computed with the one in this entry 204 if (memcmp(macResult, entry.m_mac, sizeof(cipher_block_t)) == 0) 205 { 206 // it's a match! now decrypt this entry's key in place 207 Rijndael cipher; 208 cipher.init(Rijndael::CBC, Rijndael::Decrypt, kek, Rijndael::Key16Bytes, m_header.m_iv); 209 cipher.blockDecrypt(entry.m_dek, sizeof(entry.m_dek) * 8, entry.m_dek); 210 211 m_dek = entry.m_dek; 212 memset(entry.m_dek, 0, sizeof(entry.m_dek)); // wipe the key value from memory 213 return true; 214 } 215 } 216 217 // if we exit the loop normally then no matching MAC was found 218 return false; 219 } 220 221 //! Before the boot tag is added to the #m_bootTags member, some basic checks are performed. 222 //! The command tag field is checked to make sure it matches #ROM_TAG_CMD. And 223 //! the checksum field is verified to be sure it's correct. 224 //! 225 //! After the call to this method returns, the array of boot tags is accessible 226 //! with the getBootTags() method. The array is sorted in the order in which 227 //! the boot tags appeared in the image. 228 //! 229 //! \pre Image header must have been read. 230 //! \pre Key dictionary must have been read and a valid DEK found. 231 //! \post The stream head is left pointing just after the last boot tag. 232 //! \exception read_error A failure to read the boot tag, or a failure on one 233 //! of the consistency checks will cause this exception to be thrown. 234 void EncoreBootImageReader::readBootTags() 235 { 236 assert(m_header.m_firstBootTagBlock != 0); 237 238 unsigned bootTagOffset = m_header.m_firstBootTagBlock; 239 240 while (1) 241 { 242 // seek to this boot tag and read it into a temporary buffer 243 EncoreBootImage::boot_command_t header; 244 m_stream.seekg(sizeOfCipherBlocks(bootTagOffset), std::ios_base::beg); 245 if (m_stream.read((char *)&header, sizeof(header)).bad()) 246 { 247 throw read_error("failed to read boot tag"); 248 } 249 250 // swizzle to command header 251 header.m_flags = ENDIAN_LITTLE_TO_HOST_U16(header.m_flags); 252 header.m_address = ENDIAN_LITTLE_TO_HOST_U32(header.m_address); 253 header.m_count = ENDIAN_LITTLE_TO_HOST_U32(header.m_count); 254 header.m_data = ENDIAN_LITTLE_TO_HOST_U32(header.m_data); 255 256 // decrypt in place 257 if (isEncrypted()) 258 { 259 Rijndael cipher; 260 cipher.init(Rijndael::CBC, Rijndael::Decrypt, m_dek, Rijndael::Key16Bytes, m_header.m_iv); 261 cipher.blockDecrypt((uint8_t *)&header, sizeof(header) * 8, (uint8_t *)&header); 262 } 263 264 // perform some basic checks 265 if (header.m_tag != EncoreBootImage::ROM_TAG_CMD) 266 { 267 throw read_error("boot tag is wrong command type"); 268 } 269 270 uint8_t checksum = calculateCommandChecksum(header); 271 if (checksum != header.m_checksum) 272 { 273 throw read_error("boot tag checksum is invalid"); 274 } 275 276 // save this boot tag 277 m_bootTags.push_back(header); 278 279 // and finally, update offset and break out of loop 280 bootTagOffset += header.m_count + 1; // include this boot tag in offset 281 if (header.m_flags & EncoreBootImage::ROM_LAST_TAG || bootTagOffset >= m_header.m_imageBlocks - 2) 282 { 283 break; 284 } 285 } 286 } 287 288 uint8_t EncoreBootImageReader::calculateCommandChecksum(EncoreBootImage::boot_command_t & header) 289 { 290 uint8_t * bytes = reinterpret_cast<uint8_t *>(&header); 291 uint8_t checksum = 0x5a; 292 int i; 293 294 // start at one to skip checksum field 295 for (i = 1; i < sizeof(header); ++i) 296 { 297 checksum += bytes[i]; 298 } 299 300 return checksum; 301 } 302 303 //! \param index The index of the section to read. 304 //! 305 //! \pre Both the image header and section table must have been read already before 306 //! calling this method. 307 //! \exception read_error This exception is raised if the stream reports an error while 308 //! trying to read from the section. 309 EncoreBootImage::Section * EncoreBootImageReader::readSection(unsigned index) 310 { 311 // look up section header 312 assert(index < m_sections.size()); 313 EncoreBootImage::section_header_t & header = m_sections[index]; 314 315 // seek to the section 316 m_stream.seekg(sizeOfCipherBlocks(header.m_offset), std::ios_base::beg); 317 318 uint8_t * contents = NULL; 319 try 320 { 321 // allocate memory for the section contents and read the whole thing 322 unsigned contentLength = sizeOfCipherBlocks(header.m_length); 323 contents = new uint8_t[contentLength]; 324 if (m_stream.read((char *)contents, contentLength).bad()) 325 { 326 throw read_error("failed to read section"); 327 } 328 329 // decrypt the entire section at once, if the image is encrypted and 330 // the cleartext flag is not set 331 if (isEncrypted() && (header.m_flags & EncoreBootImage::ROM_SECTION_CLEARTEXT) == 0) 332 { 333 Rijndael cipher; 334 cipher.init(Rijndael::CBC, Rijndael::Decrypt, m_dek, Rijndael::Key16Bytes, m_header.m_iv); 335 cipher.blockDecrypt(contents, contentLength * 8, contents); 336 } 337 338 // create section object 339 EncoreBootImage::Section * resultSection = NULL; 340 if (header.m_flags & EncoreBootImage::ROM_SECTION_BOOTABLE) 341 { 342 // a boot command section. 343 EncoreBootImage::BootSection * bootSection = new EncoreBootImage::BootSection(header.m_tag); 344 345 bootSection->fillFromData((cipher_block_t *)contents, header.m_length); 346 347 resultSection = bootSection; 348 } 349 else 350 { 351 // this is a raw data section 352 EncoreBootImage::DataSection * dataSection = new EncoreBootImage::DataSection(header.m_tag); 353 dataSection->setDataNoCopy(contents, contentLength); 354 contents = NULL; 355 resultSection = dataSection; 356 } 357 358 return resultSection; 359 } 360 catch (...) 361 { 362 if (contents) 363 { 364 delete [] contents; 365 } 366 throw; 367 } 368 } 369 370 371