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
This commit is contained in:
James Wilson
2022-06-04 09:37:52 -07:00
committed by GitHub
parent 23f8d829bf
commit b2082bf854
4 changed files with 183 additions and 116 deletions

View File

@@ -15,8 +15,10 @@ GNU General Public License for more details.
*/
#include <config.h>
#include <algorithm>
#include <charconv>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <ios>
@@ -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<unsigned int>(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<unsigned int>(
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<unsigned int>(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<const char *>(kd->descriptor),
ntohs(kd->length));
os.write(reinterpret_cast<const char *>(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<const char *>(kd->descriptor),
ntohs(kd->length));
os.write(reinterpret_cast<const char *>(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<const scsi::page_des&>(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<std::uint8_t>(
@@ -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<const char *>(kd->descriptor),
ntohs(kd->length));
os.write(reinterpret_cast<const char *>(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<const char *>(kd->descriptor),
ntohs(kd->length));
os.write(reinterpret_cast<const char *>(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<const scsi::page_nbes&>(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<scsi::encrypt_mode> enc_mode;
std::optional<scsi::decrypt_mode> dec_mode;
std::uint8_t algorithm_index;
std::optional<std::uint8_t> algorithm_index;
std::vector<uint8_t> 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<int>('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<const scsi::page_des&>(buffer));
if (scsi::is_device_ready(tapeDrive)) {
try {
showVolumeStatus(tapeDrive);
scsi::get_nbes(tapeDrive, buffer, sizeof(buffer));
print_volume_status(std::cout,
reinterpret_cast<const scsi::page_nbes&>(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<const scsi::page_dec&>(buffer)};
print_algorithms(std::cout, page);
exit(EXIT_SUCCESS);
print_algorithms(std::cout,
reinterpret_cast<const scsi::page_dec&>(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<unsigned int>(*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<unsigned int>(*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<const scsi::page_dec&>(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<unsigned int>(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<unsigned int>(*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<unsigned int>(
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<const scsi::page_des&>(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)

View File

@@ -16,6 +16,7 @@ GNU General Public License for more details.
#include <cerrno>
#include <cstring>
#include <functional>
#include <iomanip>
#include <iostream>
#include <sstream>
@@ -358,16 +359,17 @@ void print_sense_data(std::ostream& os, const sense_data& sd)
#endif
}
std::vector<const algorithm_descriptor *> read_algorithms(const page_dec& page)
std::vector<std::reference_wrapper<const algorithm_descriptor>>
read_algorithms(const page_dec& page)
{
auto it {reinterpret_cast<const std::uint8_t *>(&page.ads[0])};
const auto end {reinterpret_cast<const std::uint8_t *>(&page) +
ntohs(page.length) + sizeof(page_header)};
std::vector<const algorithm_descriptor *> v {};
std::vector<std::reference_wrapper<const algorithm_descriptor>> v {};
while (it < end) {
auto elem {reinterpret_cast<const algorithm_descriptor *>(it)};
v.push_back(elem);
v.push_back(std::cref(*elem));
it += ntohs(elem->length) + 4u; // length field + preceding 4 byte header
}
return v;

View File

@@ -20,7 +20,9 @@ GNU General Public License for more details.
#include <bitset>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <ostream>
#include <string>
#include <vector>
@@ -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 <typename Page>
std::vector<const kad *> read_page_kads(const Page& page)
std::vector<std::reference_wrapper<const kad>> read_page_kads(const Page& page)
{
const auto start {reinterpret_cast<const std::uint8_t *>(&page)};
auto it {start + sizeof(Page)};
const auto end {start + ntohs(page.length) + sizeof(page_header)};
std::vector<const kad *> v {};
std::vector<std::reference_wrapper<const kad>> v {};
while (it < end) {
auto elem {reinterpret_cast<const kad *>(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<const algorithm_descriptor *> read_algorithms(const page_dec& page);
std::vector<std::reference_wrapper<const algorithm_descriptor>>
read_algorithms(const page_dec& page);
} // namespace scsi

View File

@@ -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) ==