/* stenc - program to set and retrieve hardware based encryption options from certain SCSI devices (i.e. LTO4 Tape drives) Original program copyright 2010 John D. Coleman This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "scsiencrypt.h" #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif typedef struct { #if STENC_BIG_ENDIAN == 0 unsigned char bit1 : 1; unsigned char bit2 : 1; unsigned char bit3 : 1; unsigned char bit4 : 1; unsigned char bit5 : 1; unsigned char bit6 : 1; unsigned char bit7 : 1; unsigned char bit8 : 1; #else unsigned char bit8 : 1; unsigned char bit7 : 1; unsigned char bit6 : 1; unsigned char bit5 : 1; unsigned char bit4 : 1; unsigned char bit3 : 1; unsigned char bit2 : 1; unsigned char bit1 : 1; #endif } bitcheck; using namespace std; void showUsage(); void errorOut(std::string const message); void inquiryDrive(std::string tapeDevice); void showDriveStatus(std::string tapeDevice, bool detail); void showVolumeStatus(std::string tapeDevice); std::string randomKey(int length); void echo(bool); static std::optional> key_from_hex_chars(const std::string& s) { auto it = s.data(); std::vector bytes; if (s.size() % 2) { // treated as if there is an implicit leading 0 uint8_t result; auto [ptr, ec] { std::from_chars(it, it + 1, result, 16) }; if (ec != errc {}) { return {}; } bytes.push_back(result); it = ptr; } while (*it) { uint8_t result; auto [ptr, ec] { std::from_chars(it, it + 2, result, 16) }; if (ec != errc {}) { return {}; } bytes.push_back(result); it = ptr; } return bytes; } int main(int argc, char **argv) { bitcheck bc; memset(&bc, 0, 1); bc.bit2 = 1; bc.bit5 = 1; unsigned char check; memcpy(&check, &bc, 1); switch ((int)check) { case 0x12: // this is good break; case 0x48: #if STENC_BIG_ENDIAN == 1 errorOut("Swapped bit ordering detected(BI). Program needs to be " "configured without the --enable-swapendian option in order to " "function properly on your system"); #else errorOut("Swapped bit ordering detected(LI). Program needs to be " "configured with the --enable-swapendian option in order to " "function properly on your system"); #endif break; default: std::cerr << "Unknown bit check result " << std::hex << check << "\n"; errorOut("Exiting program because it will not run properly"); break; } std::string tapeDrive; int action = 0; // 0 = status, 1 =setting param, 2 = generating key std::string keyFile, keyDesc; int keyLength = 0; bool detail = false; SCSIEncryptOptions drvOptions; // First load all of the options for (int i = 1; i < argc; i++) { std::string thisCmd = argv[i]; std::string nextCmd = ""; if (i + 1 < argc) { if (strncmp(argv[i + 1], "-", 1) != 0) nextCmd = argv[i + 1]; } if (thisCmd == "--version") { std::cout << "stenc v" << VERSION << " - SCSI Tape Encryption Manager\n"; std::cout << "https://github.com/scsitape/stenc \n"; exit(EXIT_SUCCESS); } if (thisCmd == "-g") { // Check if the help flag was passed. If it was, // show usage and exit if (nextCmd == "") errorOut("Key size must be specified when using -g"); i++; // skip the next argument keyLength = std::atoi(nextCmd.c_str()); if (keyLength % 8 != 0) errorOut("Key size must be divisible by 8"); keyLength = keyLength / 8; if (keyLength > SSP_KEY_LENGTH) { std::cout << "Warning: Keys over " << (SSP_KEY_LENGTH * 8) << " bits cannot be used by this program! \n"; } action = 2; // generating key } else if (thisCmd == "-e") { if (nextCmd == "") errorOut("Key file not specified after -k option"); if (nextCmd == "on") drvOptions.cryptMode = CRYPTMODE_ON; // encrypt, read only encrypted // data else if (nextCmd == "mixed") drvOptions.cryptMode = CRYPTMODE_MIXED; // encrypt, read encrypted and unencrypted data else if (nextCmd == "rawread") drvOptions.cryptMode = CRYPTMODE_RAWREAD; // encrypt, read encrypted and unencrypted data else if (nextCmd == "off") drvOptions.cryptMode = CRYPTMODE_OFF; // encrypt, read encrypted and unencrypted data else errorOut("Unknown encryption mode '" + nextCmd + "'"); // encrypt, read encrypted and unencrypted data i++; // skip the next argument action = 1; } else if (thisCmd == "-f") { if (nextCmd == "") errorOut("Device not specified after -f option."); tapeDrive = nextCmd; // set the tape drive i++; // skip the next argument } else if (thisCmd == "-k") { if (nextCmd == "") errorOut("Key file not specified after -k option"); keyFile = nextCmd; // set the key file i++; // skip the next argument } else if (thisCmd == "-kd") { if (nextCmd == "") errorOut("Key description not specified after the -kd option"); keyDesc = nextCmd; // set the key file if (keyDesc.size() > SSP_UKAD_LENGTH) { errorOut("Key description too long!"); } i++; // skip the next argument } else if (thisCmd == "--protect") { if (drvOptions.rdmc == RDMC_UNPROTECT) errorOut("'--protect' cannot be specified at the same time as " "'--unprotect'"); drvOptions.rdmc = RDMC_PROTECT; } else if (thisCmd == "--unprotect") { if (drvOptions.rdmc == RDMC_PROTECT) errorOut("'--unprotect' cannot be specified at the same time as " "'--protect'"); drvOptions.rdmc = RDMC_UNPROTECT; } else if (thisCmd == "--ckod") { drvOptions.CKOD = true; } else if (thisCmd == "--detail") { detail = true; } else if (thisCmd == "-a") { if (nextCmd == "") errorOut("You must specify a numeric algorithm index when using the -a " "flag"); drvOptions.algorithmIndex = std::atoi(nextCmd.c_str()); i++; // skip the next argument } else { errorOut("Unknown command '" + thisCmd + "'"); } } if (action == 2) { // generate key if (keyFile == "") { errorOut("Specify file to save into with the -k argument."); } std::string const newkey = randomKey(keyLength); std::ofstream kf{}; umask(077); // make sure that no one else can read the new key file kf.open(keyFile.c_str(), std::ios::trunc); if (!kf.is_open()) { errorOut("Could not open '" + keyFile + "' for writing."); } kf << newkey << keyDesc; kf.close(); std::cout << "Random key saved into '" << keyFile << "'\n"; chmod(keyFile.c_str(), 0600); std::cout << "Permissions of keyfile set to 600\n"; exit(EXIT_SUCCESS); } // select device from env variable or system default if not given with -f if (tapeDrive.empty()) { const char *env_tape = getenv("TAPE"); if (env_tape != nullptr) { tapeDrive = env_tape; } else { tapeDrive = DEFTAPE; } } if (drvOptions.cryptMode == CRYPTMODE_RAWREAD && drvOptions.rdmc == RDMC_PROTECT) { errorOut( "'--protect' is not valid when setting encryption mode to 'rawread'"); } if (getuid() != 0) { errorOut("You must be root to read or set encryption options on a drive!"); } openlog("stenc", LOG_CONS, LOG_USER); if (action == 0) { std::cout << "Status for " << tapeDrive << "\n" << "--------------------------------------------------\n"; if (detail) inquiryDrive(tapeDrive); showDriveStatus(tapeDrive, detail); if (detail) showVolumeStatus(tapeDrive); exit(EXIT_SUCCESS); } if (drvOptions.cryptMode != CRYPTMODE_OFF) { if (keyFile == "") { std::string p1; std::string p2; bool done = false; while (!done) { std::cout << "Enter key in hex format: "; echo(false); getline(std::cin, p1); echo(true); std::cout << "\nRe-enter key in hex format: "; echo(false); getline(std::cin, p2); echo(true); std::cout << "\n"; if (p1 != p2) { std::cout << "Keys do not match!\n"; } else if (p1.empty()) { std::cout << "Key cannot be empty!\n"; } else { if (auto key_bytes = key_from_hex_chars(p1)) { std::cout << "Set encryption using this key? [y/n]: "; std::string ans = ""; getline(std::cin, ans); if (ans == "y") { drvOptions.cryptoKey = *key_bytes; done = true; } } else { std::cout << "Invalid key!\n"; } } } drvOptions.keyName = keyDesc; } else { // set keyInput here std::string keyInput; std::ifstream myfile(keyFile.c_str()); if (myfile.is_open()) { getline(myfile, keyInput); getline(myfile, keyDesc); myfile.close(); if (auto key_bytes = key_from_hex_chars(keyInput)) { drvOptions.cryptoKey = *key_bytes; } else { errorOut("Invalid key found in '" + keyFile + "'"); } drvOptions.keyName = keyDesc; } else errorOut("Could not open '" + keyFile + "' for reading"); } } // Write the options to the tape device std::cout << "Turning " << ((drvOptions.cryptMode != CRYPTMODE_OFF) ? "on" : "off") << " encryption on device '" << tapeDrive << "'..." << std::endl; bool res = SCSIWriteEncryptOptions(tapeDrive, &drvOptions); if (res) { SSP_DES *opt = SSPGetDES(tapeDrive); if (drvOptions.cryptMode != CRYPTMODE_OFF && opt->des.encryptionMode != 2) { errorOut("Turning encryption on for '" + tapeDrive + "' failed!"); } if (drvOptions.cryptMode == CRYPTMODE_OFF && opt->des.encryptionMode != 0) { errorOut("Turning encryption off for '" + tapeDrive + "' failed!"); } delete opt; if (drvOptions.cryptMode != CRYPTMODE_OFF) { std::stringstream msg; msg << "Encryption turned on for device '" << tapeDrive << "'. "; if (!drvOptions.keyName.empty()) { msg << "Key Descriptor: '" << drvOptions.keyName << "'"; } msg << " Key Instance: " << std::dec << BSLONG(opt->des.keyInstance) << std::endl; syslog(LOG_NOTICE, "%s", msg.str().c_str()); } else { std::stringstream msg{}; msg << "Encryption turned off for device '" << tapeDrive << "'."; msg << " Key Instance: " << std::dec << BSLONG(opt->des.keyInstance) << std::endl; syslog(LOG_NOTICE, "%s", msg.str().c_str()); } std::cout << "Success! See system logs for a key change audit log.\n"; exit(EXIT_SUCCESS); } if (drvOptions.cryptMode != CRYPTMODE_OFF) { errorOut("Turning encryption on for '" + tapeDrive + "' failed!"); } else { errorOut("Turning encryption off for '" + tapeDrive + "' failed!"); } } // exits to shell with an error message void errorOut(std::string const message) { std::cerr << "Error: " << message << "\n"; showUsage(); exit(EXIT_FAILURE); } // shows the command usage void showUsage() { std::cout << "Usage: stenc --version | -g -k [-kd ] | " "-f [--detail] [-e [-k ] " "[-kd ] [-a ] [--protect | --unprotect] [--ckod] ]\n\n" "Type 'man stenc' for more information.\n"; } void inquiryDrive(std::string tapeDevice) { // todo: std::cout should not be used outside main() SCSI_PAGE_INQ *const iresult = SCSIGetInquiry(tapeDevice); std::cout << std::left << std::setw(25) << "Device Mfg:"; std::cout.write((const char *)iresult->vender, 8); std::cout << std::endl; std::cout << std::left << std::setw(25) << "Product ID:"; std::cout.write((const char *)iresult->productID, 16); std::cout << std::endl; std::cout << std::left << std::setw(25) << "Product Revision:"; std::cout.write((const char *)iresult->productRev, 4); std::cout << std::endl; delete iresult; } void showDriveStatus(std::string tapeDrive, bool detail) { SSP_DES *opt = SSPGetDES(tapeDrive); if (opt == NULL) return; std::string emode = "unknown"; std::cout << std::left << std::setw(25) << "Drive Encryption:"; if ((int)opt->des.encryptionMode == 0x2 && // encrypt (int)opt->des.decryptionMode == 0x2 // read only encrypted data ) emode = "on"; if ((int)opt->des.encryptionMode == 0x2 && // encrypt (int)opt->des.decryptionMode == 0x3 // read encrypted and unencrypted ) emode = "mixed"; if ((int)opt->des.encryptionMode == 0x2 && // encrypt (int)opt->des.decryptionMode == 0x1 // read encrypted and unencrypted ) emode = "rawread"; if ((int)opt->des.encryptionMode == 0x0 && // encrypt (int)opt->des.decryptionMode == 0x0 // read encrypted and unencrypted ) emode = "off"; std::cout << emode << "\n"; if (detail) { std::cout << std::left << std::setw(25) << "Drive Output:"; switch ((int)opt->des.decryptionMode) { case 0x0: std::cout << "Not decrypting\n"; std::cout << std::setw(25) << " " << "Raw encrypted data not outputted\n"; break; case 0x1: std::cout << "Not decrypting\n"; std::cout << std::setw(25) << " " << "Raw encrypted data outputted\n"; break; case 0x2: std::cout << "Decrypting\n"; std::cout << std::setw(25) << " " << "Unencrypted data not outputted\n"; break; case 0x3: std::cout << "Decrypting\n"; std::cout << std::setw(25) << " " << "Unencrypted data outputted\n"; break; default: std::cout << "Unknown '0x" << std::hex << (int)opt->des.decryptionMode << "' \n"; break; } std::cout << std::setw(25) << "Drive Input:"; switch ((int)opt->des.encryptionMode) { case 0x0: std::cout << "Not encrypting\n"; break; case 0x2: std::cout << "Encrypting\n"; break; default: std::cout << "Unknown result '0x" << std::hex << (int)opt->des.encryptionMode << "'\n"; break; } if (opt->des.RDMD == 1) { std::cout << std::setw(25) << " " << "Protecting from raw read\n"; } std::cout << std::setw(25) << "Key Instance Counter:" << std::dec << BSLONG(opt->des.keyInstance) << "\n"; if (opt->des.algorithmIndex != 0) { std::cout << std::setw(25) << "Encryption Algorithm:" << std::hex << (int)opt->des.algorithmIndex << "\n"; } } if (opt->kads.size() > 0) { for (unsigned int i = 0; i < opt->kads.size(); i++) { std::stringstream lbl{}; lbl << "Drive Key Desc.("; switch (opt->kads[i].type) { case KAD_TYPE_UKAD: lbl << "uKAD): "; std::cout << std::setw(25) << lbl.str(); std::cout.write((const char *)&opt->kads[i].descriptor, BSSHORT(opt->kads[i].descriptorLength)); std::cout << std::endl; break; case KAD_TYPE_AKAD: lbl << "aKAD): "; std::cout << std::setw(25) << lbl.str(); std::cout.write((const char *)&opt->kads[i].descriptor, BSSHORT(opt->kads[i].descriptorLength)); std::cout << std::endl; break; } } } delete opt; } void showVolumeStatus(std::string tapeDrive) { SSP_NBES *opt = SSPGetNBES(tapeDrive, true); if (opt == NULL) return; if (opt->nbes.compressionStatus != 0) { std::cout << std::left << std::setw(25) << "Volume Compressed:"; switch (opt->nbes.compressionStatus) { case 0x00: std::cout << "Drive cannot determine\n"; break; default: std::cout << "Unknown result '" << std::hex << (int)opt->nbes.compressionStatus << "'\n"; break; } } std::cout << std::left << std::setw(25) << "Volume Encryption:"; switch ((int)opt->nbes.encryptionStatus) { case 0x01: std::cout << "Unable to determine\n"; break; case 0x02: std::cout << "Logical block is not a logical block\n"; break; case 0x03: std::cout << "Not encrypted\n"; break; case 0x05: std::cout << "Encrypted and able to decrypt\n"; if (opt->nbes.RDMDS == 1) std::cout << std::left << std::setw(25) << " Protected from raw read\n"; break; case 0x06: std::cout << "Encrypted, but unable to decrypt due to invalid key.\n"; if (opt->kads.size() > 0) { for (unsigned int i = 0; i < opt->kads.size(); i++) { std::stringstream lbl; lbl << "Volume Key Desc.("; switch (opt->kads[i].type) { case KAD_TYPE_UKAD: lbl << "uKAD): "; std::cout << std::setw(25) << lbl.str(); std::cout.write((const char *)&opt->kads[i].descriptor, BSSHORT(opt->kads[i].descriptorLength)); std::cout << std::endl; break; case KAD_TYPE_AKAD: lbl << "aKAD): "; std::cout << std::setw(25) << lbl.str(); std::cout.write((const char *)&opt->kads[i].descriptor, BSSHORT(opt->kads[i].descriptorLength)); std::cout << std::endl; break; } } } if (opt->nbes.RDMDS == 1) std::cout << std::left << std::setw(25) << " Protected from raw read\n"; break; default: std::cout << "Unknown result '" << std::hex << (int)opt->nbes.encryptionStatus << "'\n"; break; } if (opt->nbes.algorithmIndex != 0) { std::cout << std::left << std::setw(25) << "Volume Algorithm:" << (int)opt->nbes.algorithmIndex << "\n"; } delete opt; } void echo(bool on = true) { struct termios settings {}; tcgetattr(STDIN_FILENO, &settings); settings.c_lflag = on ? (settings.c_lflag | ECHO) : (settings.c_lflag & ~(ECHO)); tcsetattr(STDIN_FILENO, TCSANOW, &settings); } std::string randomKey(int length) { unsigned char rnd; std::stringstream retval{}; std::ifstream random{}; // Under Linux and AIX /dev/random provides much more cryptographically secure // random output than rand() random.open("/dev/random", std::ios::in | std::ios::binary); if (random.is_open()) { for (int i = 0; i < length; i++) { random.read(reinterpret_cast(&rnd), 1); retval << std::hex << std::setfill('0') << setw(2) << static_cast(rnd); } random.close(); } else { std::cout << "Enter random keys on the keyboard to seed the generator.\n" "End by pressing enter...\n"; double check = 0; char c = 0; echo(false); while (c != 10) { check += (int)c; c = getchar(); } echo(true); srand(time(NULL) + (int)check); for (int i = 0; i < length; i++) { retval << std::hex << (std::rand() % 256); } } retval << std::endl; return (retval.str()); }