From b2082bf85482db24ace64bece50f1c5619d0eeef Mon Sep 17 00:00:00 2001 From: James Wilson Date: Sat, 4 Jun 2022 09:37:52 -0700 Subject: [PATCH] Do validation of options against device capabilities (#83) * Validate options against device capabilities * Use std::reference_wrapper instead of raw pointers * Reword error message * Also validate --(no-)allow-raw-read against device capabilities * --ckod should do a media check --- src/main.cpp | 231 ++++++++++++++++++++++++++------------------ src/scsiencrypt.cpp | 8 +- src/scsiencrypt.h | 38 +++++++- tests/scsi.cpp | 22 ++--- 4 files changed, 183 insertions(+), 116 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 130cc5b..fa4c7c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,8 +15,10 @@ GNU General Public License for more details. */ #include +#include #include #include +#include #include #include #include @@ -114,7 +116,7 @@ encryption status and capabilities of DEVICE, including a list of supported\n\ algorithm indexes.\n"; } -static void print_algorithm_name(std::ostream& os, const uint32_t code) +static void print_algorithm_name(std::ostream& os, const std::uint32_t code) { // Reference: SFSC / INCITS 501-2016 if (0x80010400 <= code && code <= 0x8001FFFF) { @@ -146,8 +148,7 @@ static void print_algorithms(std::ostream& os, const scsi::page_dec& page) os << "Supported algorithms:\n"; - for (auto ad_ptr: algorithms) { - auto& ad {*ad_ptr}; + for (const scsi::algorithm_descriptor& ad: algorithms) { os << std::left << std::setw(5) << static_cast(ad.algorithm_index); print_algorithm_name(os, ntohl(ad.security_algorithm_code)); @@ -156,7 +157,7 @@ static void print_algorithms(std::ostream& os, const scsi::page_dec& page) // Print KAD capabilities and size auto dkad_c {static_cast( ad.flags3 & scsi::algorithm_descriptor::flags3_dkad_c_mask)}; - if (dkad_c == 1u << scsi::algorithm_descriptor::flags3_dkad_c_pos) { + if (dkad_c == 2u << scsi::algorithm_descriptor::flags3_dkad_c_pos) { os << std::left << std::setw(5) << "" << "Key descriptors not allowed\n"; } else if (dkad_c) { @@ -213,13 +214,6 @@ static void print_device_inquiry(std::ostream& os, os.put('\n'); } -static void inquiryDrive(const std::string& tapeDevice) -{ - // todo: std::cout should not be used outside main() - auto iresult {scsi::get_inquiry(tapeDevice)}; - print_device_inquiry(std::cout, iresult); -} - static void print_device_status(std::ostream& os, const scsi::page_des& opt) { os << std::left << std::setw(25) << "Drive Output:"; @@ -275,33 +269,22 @@ static void print_device_status(std::ostream& os, const scsi::page_des& opt) << static_cast(opt.algorithm_index) << '\n'; } auto kads {scsi::read_page_kads(opt)}; - for (auto kd: kads) { - switch (kd->type) { + for (const scsi::kad& kd: kads) { + switch (kd.type) { case scsi::kad_type::ukad: os << std::setw(25) << "Drive Key Desc.(uKAD): "; - os.write(reinterpret_cast(kd->descriptor), - ntohs(kd->length)); + os.write(reinterpret_cast(kd.descriptor), ntohs(kd.length)); os.put('\n'); break; case scsi::kad_type::akad: os << std::setw(25) << "Drive Key Desc.(aKAD): "; - os.write(reinterpret_cast(kd->descriptor), - ntohs(kd->length)); + os.write(reinterpret_cast(kd.descriptor), ntohs(kd.length)); os.put('\n'); break; } } } -static void showDriveStatus(const std::string& tapeDrive) -{ - alignas(4) scsi::page_buffer buffer; - scsi::get_des(tapeDrive, buffer, sizeof(buffer)); - auto& opt {reinterpret_cast(buffer)}; - - print_device_status(std::cout, opt); -} - static void print_volume_status(std::ostream& os, const scsi::page_nbes& opt) { auto compression_status {static_cast( @@ -343,18 +326,18 @@ static void print_volume_status(std::ostream& os, const scsi::page_nbes& opt) break; case 6u << scsi::page_nbes::status_encryption_pos: os << "Encrypted, but unable to decrypt due to invalid key.\n"; - for (auto kd: kads) { - switch (kd->type) { + for (const scsi::kad& kd: kads) { + switch (kd.type) { case scsi::kad_type::ukad: os << std::setw(25) << "Volume Key Desc.(uKAD): "; - os.write(reinterpret_cast(kd->descriptor), - ntohs(kd->length)); + os.write(reinterpret_cast(kd.descriptor), + ntohs(kd.length)); os.put('\n'); break; case scsi::kad_type::akad: os << std::setw(25) << "Volume Key Desc.(aKAD): "; - os.write(reinterpret_cast(kd->descriptor), - ntohs(kd->length)); + os.write(reinterpret_cast(kd.descriptor), + ntohs(kd.length)); os.put('\n'); break; } @@ -376,15 +359,6 @@ static void print_volume_status(std::ostream& os, const scsi::page_nbes& opt) } } -static void showVolumeStatus(const std::string& tapeDrive) -{ - alignas(4) scsi::page_buffer buffer; - scsi::get_nbes(tapeDrive, buffer, sizeof(buffer)); - auto& opt {reinterpret_cast(buffer)}; - - print_volume_status(std::cout, opt); -} - static void echo(bool on) { struct termios settings {}; @@ -402,12 +376,14 @@ int main(int argc, char **argv) std::optional enc_mode; std::optional dec_mode; - std::uint8_t algorithm_index; + std::optional algorithm_index; std::vector key; std::string key_name; scsi::sde_rdmc rdmc {}; bool ckod {}; + alignas(4) scsi::page_buffer buffer {}; + enum opt_key : int { opt_version = 256, opt_ckod, @@ -446,7 +422,7 @@ int main(int argc, char **argv) dec_mode = scsi::decrypt_mode::mixed; } else { print_usage(std::cerr); - exit(EXIT_FAILURE); + std::exit(EXIT_FAILURE); } break; } @@ -458,7 +434,7 @@ int main(int argc, char **argv) enc_mode = scsi::encrypt_mode::off; } else { print_usage(std::cerr); - exit(EXIT_FAILURE); + std::exit(EXIT_FAILURE); } break; } @@ -477,21 +453,21 @@ int main(int argc, char **argv) case opt_rdmc_disable: rdmc = scsi::sde_rdmc::disabled; break; - case static_cast('h'): + case 'h': print_usage(std::cout); - exit(EXIT_SUCCESS); + std::exit(EXIT_SUCCESS); case opt_version: std::cout << "stenc " VERSION " - SCSI Tape Encryption Manager\n" << "https://github.com/scsitape/stenc\n"; - exit(EXIT_SUCCESS); + std::exit(EXIT_SUCCESS); default: print_usage(std::cerr); - exit(EXIT_FAILURE); + std::exit(EXIT_FAILURE); } } if (optind != argc) { // left-over unparsed arguments or options print_usage(std::cerr); - exit(EXIT_FAILURE); + std::exit(EXIT_FAILURE); } // select device from env variable or system default if not given with -f @@ -511,11 +487,15 @@ int main(int argc, char **argv) << "--------------------------------------------------\n"; try { - inquiryDrive(tapeDrive); - showDriveStatus(tapeDrive); + print_device_inquiry(std::cout, scsi::get_inquiry(tapeDrive)); + scsi::get_des(tapeDrive, buffer, sizeof(buffer)); + print_device_status(std::cout, + reinterpret_cast(buffer)); if (scsi::is_device_ready(tapeDrive)) { try { - showVolumeStatus(tapeDrive); + scsi::get_nbes(tapeDrive, buffer, sizeof(buffer)); + print_volume_status(std::cout, + reinterpret_cast(buffer)); } catch (const scsi::scsi_error& err) { // #71: ignore BLANK CHECK sense key that some drives may return // during media access check in getting NBES @@ -526,19 +506,17 @@ int main(int argc, char **argv) } } } - alignas(4) scsi::page_buffer buffer {}; scsi::get_dec(tapeDrive, buffer, sizeof(buffer)); - auto& page {reinterpret_cast(buffer)}; - - print_algorithms(std::cout, page); - exit(EXIT_SUCCESS); + print_algorithms(std::cout, + reinterpret_cast(buffer)); + std::exit(EXIT_SUCCESS); } catch (const scsi::scsi_error& err) { std::cerr << "stenc: " << err.what() << '\n'; scsi::print_sense_data(std::cerr, err.get_sense()); - exit(EXIT_FAILURE); + std::exit(EXIT_FAILURE); } catch (const std::runtime_error& err) { std::cerr << "stenc: " << err.what() << '\n'; - exit(EXIT_FAILURE); + std::exit(EXIT_FAILURE); } } @@ -553,7 +531,7 @@ int main(int argc, char **argv) } else { std::cerr << "stenc: Unexpected encrypt mode " << static_cast(*enc_mode) << '\n'; - exit(EXIT_FAILURE); + std::exit(EXIT_FAILURE); } } else if (!enc_mode && dec_mode) { if (dec_mode == scsi::decrypt_mode::off) { @@ -566,7 +544,7 @@ int main(int argc, char **argv) } else { std::cerr << "stenc: Unexpected decrypt mode " << static_cast(*dec_mode) << '\n'; - exit(EXIT_FAILURE); + std::exit(EXIT_FAILURE); } } @@ -574,7 +552,7 @@ int main(int argc, char **argv) dec_mode != scsi::decrypt_mode::off) { if (keyFile.empty()) { std::cerr << "stenc: Encryption key required but no key file specified\n"; - exit(EXIT_FAILURE); + std::exit(EXIT_FAILURE); } // set keyInput here @@ -582,7 +560,7 @@ int main(int argc, char **argv) if (keyFile == "-"s) { // Read key file from standard input if (isatty(STDIN_FILENO)) { - std::cout << "Enter key (input will be hidden): "; + std::cout << "Enter key in hex format (input will be hidden): "; echo(false); } std::getline(std::cin, keyInput); @@ -596,7 +574,7 @@ int main(int argc, char **argv) if (!myfile.is_open()) { std::cerr << "stenc: Cannot open " << keyFile << ": " << strerror(errno) << '\n'; - exit(EXIT_FAILURE); + std::exit(EXIT_FAILURE); } std::getline(myfile, keyInput); std::getline(myfile, key_name); @@ -606,53 +584,114 @@ int main(int argc, char **argv) key = *key_bytes; } else { std::cerr << "stenc: Invalid key in key file\n"; + std::exit(EXIT_FAILURE); + } + } + + try { + scsi::get_dec(tapeDrive, buffer, sizeof(buffer)); + auto& dec_page {reinterpret_cast(buffer)}; + auto algorithms {scsi::read_algorithms(dec_page)}; + + if (algorithm_index == std::nullopt) { + if (algorithms.size() == 1) { + // Pick the only available algorithm if not specified + const scsi::algorithm_descriptor& ad = algorithms[0]; + std::cerr << "Algorithm index not specified, using " << std::dec + << static_cast(ad.algorithm_index) << " ("; + print_algorithm_name(std::cerr, ntohl(ad.security_algorithm_code)); + std::cerr << ")\n"; + algorithm_index = ad.algorithm_index; + } else { + std::cerr << "stenc: Algorithm index not specified\n"; + print_algorithms(std::cerr, dec_page); + std::exit(EXIT_FAILURE); + } + } + + auto algo_it { + std::find_if(algorithms.begin(), algorithms.end(), + [algorithm_index](const scsi::algorithm_descriptor& ad) { + return ad.algorithm_index == algorithm_index; + })}; + if (algo_it == algorithms.end()) { + std::cerr << "stenc: Algorithm index " << std::dec + << static_cast(*algorithm_index) + << " not supported by device\n"; + std::exit(EXIT_FAILURE); + } + const scsi::algorithm_descriptor& ad = *algo_it; + + if ((enc_mode != scsi::encrypt_mode::off || + dec_mode != scsi::decrypt_mode::off) && + key.size() != ntohs(ad.key_length)) { + std::cerr << "stenc: Incorrect key size, expected " << std::dec + << ntohs(ad.key_length) << " bytes, got " << key.size() << '\n'; + std::exit(EXIT_FAILURE); + } + + if (key_name.size() > ntohs(ad.maximum_ukad_length)) { + std::cerr << "stenc: Key descriptor exceeds maximum length of " + << std::dec << ntohs(ad.maximum_ukad_length) << " bytes\n"; + std::exit(EXIT_FAILURE); + } + + bool ukad_fixed = + (ad.flags2 & scsi::algorithm_descriptor::flags2_ukadf_mask) == + scsi::algorithm_descriptor::flags2_ukadf_mask; + if (ukad_fixed && key_name.size() < ntohs(ad.maximum_ukad_length)) { + // Pad key descriptor to required length + key_name.resize(ntohs(ad.maximum_ukad_length), ' '); + } + + if (enc_mode != scsi::encrypt_mode::on) { + // key descriptor only valid when key is used for writing + key_name.erase(); + } + + if (rdmc != scsi::sde_rdmc {}) { + auto rdmc_c {static_cast( + ad.flags3 & scsi::algorithm_descriptor::flags3_rdmc_c_mask)}; + if (rdmc_c == 6u << scsi::algorithm_descriptor::flags3_rdmc_c_pos || + rdmc_c == 7u << scsi::algorithm_descriptor::flags3_rdmc_c_pos) { + std::cerr << "stenc: Device does not allow control of raw reads\n"; + exit(EXIT_FAILURE); + } + } + + if (ckod && !scsi::is_device_ready(tapeDrive)) { + std::cerr << "stenc: Cannot use --ckod when no tape media is loaded\n"; exit(EXIT_FAILURE); } - } - if (enc_mode != scsi::encrypt_mode::on) { - key_name.erase(); // key descriptor only valid when key is used for writing - } - - // Write the options to the tape device - std::cerr << "Changing encryption settings on device " << tapeDrive << '\n'; - try { + // Write the options to the tape device + std::cerr << "Changing encryption settings for device " << tapeDrive + << "...\n"; auto sde_buffer {scsi::make_sde(enc_mode.value(), dec_mode.value(), - algorithm_index, key, key_name, rdmc, - ckod)}; + algorithm_index.value(), key, key_name, + rdmc, ckod)}; scsi::write_sde(tapeDrive, sde_buffer.get()); - - alignas(4) scsi::page_buffer buffer {}; scsi::get_des(tapeDrive, buffer, sizeof(buffer)); auto& opt {reinterpret_cast(buffer)}; + std::ostringstream oss; - if (enc_mode != scsi::encrypt_mode::off) { - std::stringstream msg; - msg << "Encryption turned on for device '" << tapeDrive << "'. "; - if (!key_name.empty()) { - msg << "Key Descriptor: '" << key_name << "'"; - } - msg << " Key Instance: " << std::dec << ntohl(opt.key_instance_counter) - << '\n'; - - syslog(LOG_NOTICE, "%s", msg.str().c_str()); - } else { - std::stringstream msg {}; - - msg << "Encryption turned off for device '" << tapeDrive << "'."; - msg << " Key Instance: " << std::dec << ntohl(opt.key_instance_counter) - << '\n'; - - syslog(LOG_NOTICE, "%s", msg.str().c_str()); + oss << "Encryption settings changed for device " << tapeDrive + << ": mode: encrypt = " << enc_mode.value() + << ", decrypt = " << dec_mode.value() << '.'; + if (!key_name.empty()) { + oss << " Key Descriptor: '" << key_name << "',"; } + oss << " Key Instance Counter: " << std::dec + << ntohl(opt.key_instance_counter) << '\n'; + syslog(LOG_NOTICE, "%s", oss.str().c_str()); std::cerr << "Success! See system logs for a key change audit log.\n"; } catch (const scsi::scsi_error& err) { std::cerr << "stenc: " << err.what() << '\n'; scsi::print_sense_data(std::cerr, err.get_sense()); - exit(EXIT_FAILURE); + std::exit(EXIT_FAILURE); } catch (const std::runtime_error& err) { std::cerr << "stenc: " << err.what() << '\n'; - exit(EXIT_FAILURE); + std::exit(EXIT_FAILURE); } } #endif // defined(CATCH_CONFIG_MAIN) diff --git a/src/scsiencrypt.cpp b/src/scsiencrypt.cpp index fd66970..03fec45 100644 --- a/src/scsiencrypt.cpp +++ b/src/scsiencrypt.cpp @@ -16,6 +16,7 @@ GNU General Public License for more details. #include #include +#include #include #include #include @@ -358,16 +359,17 @@ void print_sense_data(std::ostream& os, const sense_data& sd) #endif } -std::vector read_algorithms(const page_dec& page) +std::vector> +read_algorithms(const page_dec& page) { auto it {reinterpret_cast(&page.ads[0])}; const auto end {reinterpret_cast(&page) + ntohs(page.length) + sizeof(page_header)}; - std::vector v {}; + std::vector> v {}; while (it < end) { auto elem {reinterpret_cast(it)}; - v.push_back(elem); + v.push_back(std::cref(*elem)); it += ntohs(elem->length) + 4u; // length field + preceding 4 byte header } return v; diff --git a/src/scsiencrypt.h b/src/scsiencrypt.h index d9d1bda..d83c973 100644 --- a/src/scsiencrypt.h +++ b/src/scsiencrypt.h @@ -20,7 +20,9 @@ GNU General Public License for more details. #include #include #include +#include #include +#include #include #include @@ -35,7 +37,6 @@ GNU General Public License for more details. #endif constexpr std::size_t SSP_PAGE_ALLOCATION = 8192; -constexpr std::size_t SSP_UKAD_LENGTH = 0x1e; // outputs hex in a 2 digit pair #define HEX(x) \ @@ -50,6 +51,18 @@ enum class encrypt_mode : std::uint8_t { on = 2u, }; +inline std::ostream& operator<<(std::ostream& os, encrypt_mode m) +{ + if (m == encrypt_mode::off) { + os << "off"; + } else if (m == encrypt_mode::external) { + os << "external"; + } else { + os << "on"; + } + return os; +} + enum class decrypt_mode : std::uint8_t { off = 0u, raw = 1u, @@ -57,6 +70,20 @@ enum class decrypt_mode : std::uint8_t { mixed = 3u, }; +inline std::ostream& operator<<(std::ostream& os, decrypt_mode m) +{ + if (m == decrypt_mode::off) { + os << "off"; + } else if (m == decrypt_mode::raw) { + os << "raw"; + } else if (m == decrypt_mode::on) { + os << "on"; + } else { + os << "mixed"; + } + return os; +} + enum class kad_type : std::uint8_t { ukad = 0u, // unauthenticated key-associated data akad = 1u, // authenticated key-associated data @@ -358,16 +385,16 @@ private: // Extract pointers to kad structures within a variable-length page. // Page must have a page_header layout template -std::vector read_page_kads(const Page& page) +std::vector> read_page_kads(const Page& page) { const auto start {reinterpret_cast(&page)}; auto it {start + sizeof(Page)}; const auto end {start + ntohs(page.length) + sizeof(page_header)}; - std::vector v {}; + std::vector> v {}; while (it < end) { auto elem {reinterpret_cast(it)}; - v.push_back(elem); + v.push_back(std::cref(*elem)); it += ntohs(elem->length) + sizeof(kad); } return v; @@ -396,7 +423,8 @@ make_sde(encrypt_mode enc_mode, decrypt_mode dec_mode, // Write set data encryption parameters to device void write_sde(const std::string& device, const std::uint8_t *sde_buffer); void print_sense_data(std::ostream& os, const sense_data& sd); -std::vector read_algorithms(const page_dec& page); +std::vector> +read_algorithms(const page_dec& page); } // namespace scsi diff --git a/tests/scsi.cpp b/tests/scsi.cpp index 654685a..1b721d6 100644 --- a/tests/scsi.cpp +++ b/tests/scsi.cpp @@ -208,11 +208,10 @@ TEST_CASE("Interpret device encryption status page", "[scsi]") auto kads = read_page_kads(page_des); REQUIRE(kads.size() == 1u); - REQUIRE((kads[0]->flags & scsi::kad::flags_authenticated_mask) == - std::byte {1u}); - REQUIRE(ntohs(kads[0]->length) == std::strlen("Hello world!")); - REQUIRE(std::memcmp(kads[0]->descriptor, "Hello world!", - ntohs(kads[0]->length)) == 0); + const scsi::kad& kd = kads[0]; + REQUIRE((kd.flags & scsi::kad::flags_authenticated_mask) == std::byte {1u}); + REQUIRE(ntohs(kd.length) == std::strlen("Hello world!")); + REQUIRE(std::memcmp(kd.descriptor, "Hello world!", ntohs(kd.length)) == 0); } TEST_CASE("Interpret next block encryption status page", "[scsi]") @@ -248,11 +247,10 @@ TEST_CASE("Interpret next block encryption status page", "[scsi]") auto kads = read_page_kads(page_nbes); REQUIRE(kads.size() == 1u); - REQUIRE((kads[0]->flags & scsi::kad::flags_authenticated_mask) == - std::byte {1u}); - REQUIRE(ntohs(kads[0]->length) == std::strlen("Hello world!")); - REQUIRE(std::memcmp(kads[0]->descriptor, "Hello world!", - ntohs(kads[0]->length)) == 0); + const scsi::kad& kd = kads[0]; + REQUIRE((kd.flags & scsi::kad::flags_authenticated_mask) == std::byte {1u}); + REQUIRE(ntohs(kd.length) == std::strlen("Hello world!")); + REQUIRE(std::memcmp(kd.descriptor, "Hello world!", ntohs(kd.length)) == 0); } TEST_CASE("Interpret data encryption capabilties page", "[scsi]") @@ -311,7 +309,7 @@ TEST_CASE("Interpret data encryption capabilties page", "[scsi]") auto algorithms {read_algorithms(page_dec)}; REQUIRE(algorithms.size() == 2u); - auto& algo1 {*algorithms[0]}; + const scsi::algorithm_descriptor& algo1 = algorithms[0]; REQUIRE(algo1.algorithm_index == 1u); REQUIRE(ntohs(algo1.length) == 20u); REQUIRE((algo1.flags1 & scsi::algorithm_descriptor::flags1_avfmv_mask) == @@ -359,7 +357,7 @@ TEST_CASE("Interpret data encryption capabilties page", "[scsi]") REQUIRE(ntohs(algo1.maximum_eedk_size) == 0u); REQUIRE(ntohl(algo1.security_algorithm_code) == 0x00010014u); - auto& algo2 {*algorithms[1]}; + const scsi::algorithm_descriptor& algo2 = algorithms[1]; REQUIRE(algo2.algorithm_index == 2u); REQUIRE(ntohs(algo2.length) == 20u); REQUIRE((algo2.flags1 & scsi::algorithm_descriptor::flags1_avfmv_mask) ==