void EncoreBootImage::prepareImageHeader(boot_image_header_t & header) { // get identifier for the first bootable section Section * firstBootSection = findFirstBootableSection(); section_id_t firstBootSectionID = 0; if (firstBootSection) { firstBootSectionID = firstBootSection->getIdentifier(); } // fill in header fields header.m_signature[0] = 'S'; header.m_signature[1] = 'T'; header.m_signature[2] = 'M'; header.m_signature[3] = 'P'; header.m_majorVersion = ROM_BOOT_IMAGE_MAJOR_VERSION; header.m_minorVersion = ROM_BOOT_IMAGE_MINOR_VERSION; header.m_flags = ENDIAN_HOST_TO_LITTLE_U16(m_headerFlags); header.m_imageBlocks = ENDIAN_HOST_TO_LITTLE_U32(getImageSize()); header.m_firstBootableSectionID = ENDIAN_HOST_TO_LITTLE_U32(firstBootSectionID); header.m_keyCount = ENDIAN_HOST_TO_LITTLE_U16((uint16_t)m_keys.size()); header.m_headerBlocks = ENDIAN_HOST_TO_LITTLE_U16((uint16_t)numberOfCipherBlocks(sizeof(header))); header.m_sectionCount = ENDIAN_HOST_TO_LITTLE_U16((uint16_t)m_sections.size()); header.m_sectionHeaderSize = ENDIAN_HOST_TO_LITTLE_U16((uint16_t)numberOfCipherBlocks(sizeof(section_header_t))); header.m_signature2[0] = 's'; header.m_signature2[1] = 'g'; header.m_signature2[2] = 't'; header.m_signature2[3] = 'l'; header.m_timestamp = ENDIAN_HOST_TO_LITTLE_U64(getTimestamp()); header.m_driveTag = m_driveTag; // Prepare version fields by converting them to the correct byte order. header.m_productVersion = m_productVersion; header.m_componentVersion = m_componentVersion; header.m_productVersion.fixByteOrder(); header.m_componentVersion.fixByteOrder(); // the fields are dependant on others header.m_keyDictionaryBlock = ENDIAN_HOST_TO_LITTLE_U16(header.m_headerBlocks + header.m_sectionCount * header.m_sectionHeaderSize); header.m_firstBootTagBlock = ENDIAN_HOST_TO_LITTLE_U32(header.m_keyDictionaryBlock + header.m_keyCount * 2); // generate random pad bytes RandomNumberGenerator rng; rng.generateBlock(header.m_padding0, sizeof(header.m_padding0)); rng.generateBlock(header.m_padding1, sizeof(header.m_padding1)); // compute SHA-1 digest over the image header uint8_t * message = reinterpret_cast<uint8_t *>(&header.m_signature); uint32_t length = static_cast<uint32_t>(sizeof(header) - sizeof(header.m_digest)); // include padding CSHA1 hash; hash.Reset(); hash.Update(message, length); hash.Final(); hash.GetHash(header.m_digest); }
//! Fills #m_padding with random bytes that may be used to fill up the last data //! cipher block. void EncoreBootImage::LoadCommand::fillPadding() { RandomNumberGenerator rng; rng.generateBlock(m_padding, sizeof(m_padding)); }
//! \todo Optimize writing section data. Right now it only writes one block at a //! time, which is of course quite slow (in relative terms). //! \todo Refactor this into several different methods for writing each region //! of the image. Use a context structure to keep track of shared data between //! each of the methods. //! \todo Refactor the section and boot tag writing code to only have a single //! copy of the block writing and encryption loop. void EncoreBootImage::writeToStream(std::ostream & stream) { // always generate the session key or DEK even if image is unencrypted m_sessionKey.randomize(); // prepare to compute CBC-MACs with each KEK unsigned i; smart_array_ptr<RijndaelCBCMAC> macs(0); if (isEncrypted()) { macs = new RijndaelCBCMAC[m_keys.size()]; for (i=0; i < m_keys.size(); ++i) { RijndaelCBCMAC mac(m_keys[i]); (macs.get())[i] = mac; } } // prepare to compute SHA-1 digest over entire image CSHA1 hash; hash.Reset(); // count of total blocks written to the file unsigned fileBlocksWritten = 0; // we need some pieces of the header down below boot_image_header_t imageHeader; prepareImageHeader(imageHeader); // write plaintext header { // write header assert(sizeOfPaddingForCipherBlocks(sizeof(boot_image_header_t)) == 0); stream.write(reinterpret_cast<char *>(&imageHeader), sizeof(imageHeader)); fileBlocksWritten += numberOfCipherBlocks(sizeof(imageHeader)); // update CBC-MAC over image header if (isEncrypted()) { for (i=0; i < m_keys.size(); ++i) { (macs.get())[i].update(reinterpret_cast<uint8_t *>(&imageHeader), sizeof(imageHeader)); } } // update SHA-1 hash.Update(reinterpret_cast<uint8_t *>(&imageHeader), sizeof(imageHeader)); } // write plaintext section table { section_iterator_t it = beginSection(); for (; it != endSection(); ++it) { Section * section = *it; // write header for this section assert(sizeOfPaddingForCipherBlocks(sizeof(section_header_t)) == 0); section_header_t sectionHeader; section->fillSectionHeader(sectionHeader); stream.write(reinterpret_cast<char *>(§ionHeader), sizeof(sectionHeader)); fileBlocksWritten += numberOfCipherBlocks(sizeof(sectionHeader)); // update CBC-MAC over this entry if (isEncrypted()) { for (i=0; i < m_keys.size(); ++i) { (macs.get())[i].update(reinterpret_cast<uint8_t *>(§ionHeader), sizeof(sectionHeader)); } } // update SHA-1 hash.Update(reinterpret_cast<uint8_t *>(§ionHeader), sizeof(sectionHeader)); } } // finished with the CBC-MAC if (isEncrypted()) { for (i=0; i < m_keys.size(); ++i) { (macs.get())[i].finalize(); } } // write key dictionary if (isEncrypted()) { key_iterator_t it = beginKeys(); for (i=0; it != endKeys(); ++it, ++i) { // write CBC-MAC result for this key, then update SHA-1 RijndaelCBCMAC & mac = (macs.get())[i]; const RijndaelCBCMAC::block_t & macResult = mac.getMAC(); stream.write(reinterpret_cast<const char *>(&macResult), sizeof(RijndaelCBCMAC::block_t)); hash.Update(reinterpret_cast<const uint8_t *>(&macResult), sizeof(RijndaelCBCMAC::block_t)); fileBlocksWritten++; // encrypt DEK with this key, write it out, and update image digest Rijndael cipher; cipher.init(Rijndael::CBC, Rijndael::Encrypt, *it, Rijndael::Key16Bytes, imageHeader.m_iv); AESKey<128>::key_t wrappedSessionKey; cipher.blockEncrypt(m_sessionKey, sizeof(AESKey<128>::key_t) * 8, wrappedSessionKey); stream.write(reinterpret_cast<char *>(&wrappedSessionKey), sizeof(wrappedSessionKey)); hash.Update(reinterpret_cast<uint8_t *>(&wrappedSessionKey), sizeof(wrappedSessionKey)); fileBlocksWritten++; } } // write sections and boot tags { section_iterator_t it = beginSection(); for (; it != endSection(); ++it) { section_iterator_t itCopy = it; bool isLastSection = (++itCopy == endSection()); Section * section = *it; cipher_block_t block; unsigned blockCount = section->getBlockCount(); unsigned blocksWritten = 0; Rijndael cipher; cipher.init(Rijndael::CBC, Rijndael::Encrypt, m_sessionKey, Rijndael::Key16Bytes, imageHeader.m_iv); // Compute the number of padding blocks needed to align the section. This first // call to getPadBlockCountForOffset() passes an offset that excludes // the boot tag for this section. unsigned paddingBlocks = getPadBlockCountForSection(section, fileBlocksWritten); // Insert nop commands as padding to align the start of the section, if // the section has special alignment requirements. NopCommand nop; while (paddingBlocks--) { blockCount = nop.getBlockCount(); blocksWritten = 0; while (blocksWritten < blockCount) { nop.getBlocks(blocksWritten, 1, &block); if (isEncrypted()) { // re-init after encrypt to update IV cipher.blockEncrypt(block, sizeof(cipher_block_t) * 8, block); cipher.init(Rijndael::CBC, Rijndael::Encrypt, m_sessionKey, Rijndael::Key16Bytes, block); } stream.write(reinterpret_cast<char *>(&block), sizeof(cipher_block_t)); hash.Update(reinterpret_cast<uint8_t *>(&block), sizeof(cipher_block_t)); blocksWritten++; fileBlocksWritten++; } } // reinit cipher for boot tag cipher.init(Rijndael::CBC, Rijndael::Encrypt, m_sessionKey, Rijndael::Key16Bytes, imageHeader.m_iv); // write boot tag TagCommand tag(*section); tag.setLast(isLastSection); if (!isLastSection) { // If this isn't the last section, the tag needs to include any // padding for the next section in its length, otherwise the ROM // won't be able to find the next section's boot tag. unsigned nextSectionOffset = fileBlocksWritten + section->getBlockCount() + 1; tag.setSectionLength(section->getBlockCount() + getPadBlockCountForSection(*itCopy, nextSectionOffset)); } blockCount = tag.getBlockCount(); blocksWritten = 0; while (blocksWritten < blockCount) { tag.getBlocks(blocksWritten, 1, &block); if (isEncrypted()) { // re-init after encrypt to update IV cipher.blockEncrypt(block, sizeof(cipher_block_t) * 8, block); cipher.init(Rijndael::CBC, Rijndael::Encrypt, m_sessionKey, Rijndael::Key16Bytes, block); } stream.write(reinterpret_cast<char *>(&block), sizeof(cipher_block_t)); hash.Update(reinterpret_cast<uint8_t *>(&block), sizeof(cipher_block_t)); blocksWritten++; fileBlocksWritten++; } // reinit cipher for section data cipher.init(Rijndael::CBC, Rijndael::Encrypt, m_sessionKey, Rijndael::Key16Bytes, imageHeader.m_iv); // write section data blockCount = section->getBlockCount(); blocksWritten = 0; while (blocksWritten < blockCount) { section->getBlocks(blocksWritten, 1, &block); // Only encrypt the section contents if the entire boot image is encrypted // and the section doesn't have the "leave unencrypted" flag set. Even if the // section is unencrypted the boot tag will remain encrypted. if (isEncrypted() && !section->getLeaveUnencrypted()) { // re-init after encrypt to update IV cipher.blockEncrypt(block, sizeof(cipher_block_t) * 8, block); cipher.init(Rijndael::CBC, Rijndael::Encrypt, m_sessionKey, Rijndael::Key16Bytes, block); } stream.write(reinterpret_cast<char *>(&block), sizeof(cipher_block_t)); hash.Update(reinterpret_cast<uint8_t *>(&block), sizeof(cipher_block_t)); blocksWritten++; fileBlocksWritten++; } } } // write SHA-1 digest over entire image { // allocate enough room for digest and bytes to pad out to the next cipher block const unsigned padBytes = sizeOfPaddingForCipherBlocks(sizeof(sha1_digest_t)); unsigned digestBlocksSize = sizeof(sha1_digest_t) + padBytes; smart_array_ptr<uint8_t> digestBlocks = new uint8_t[digestBlocksSize]; hash.Final(); hash.GetHash(digestBlocks.get()); // set the pad bytes to random values RandomNumberGenerator rng; rng.generateBlock(&(digestBlocks.get())[sizeof(sha1_digest_t)], padBytes); // encrypt with session key if (isEncrypted()) { Rijndael cipher; cipher.init(Rijndael::CBC, Rijndael::Encrypt, m_sessionKey, Rijndael::Key16Bytes, imageHeader.m_iv); cipher.blockEncrypt(digestBlocks.get(), digestBlocksSize * 8, digestBlocks.get()); } // write to the stream stream.write(reinterpret_cast<char *>(digestBlocks.get()), digestBlocksSize); } }