Files
stenc/src/scsiencrypt.cpp
2020-04-13 12:11:26 +02:00

627 lines
16 KiB
C++

/*
Header file to send and recieve SPIN/SPOUT commands to SCSI device
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 <config.h>
#include <string>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <bitset>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#if defined(OS_LINUX)
#include <scsi/sg.h>
#include <scsi/scsi.h>
#define SCSI_TIMEOUT 5000
#elif defined(OS_FREEBSD)
#include <cam/scsi/scsi_sg.h>
#define SCSI_TIMEOUT 5000
#elif defined(OS_AIX)
#define _LINUX_SOURCE_COMPAT
#include <sys/scsi.h>
#include <sys/scsi_buf.h>
#include <sys/tape.h>
#include <sys/Atape.h>
#define SCSI_TIMEOUT 5
#else
#error "OS type is not set"
#endif
#include <sys/mtio.h>
#include "scsiencrypt.h"
#define SSP_SPIN_OPCODE 0XA2
#define SSP_SPOUT_OPCODE 0XB5
#define SSP_SP_CMD_LEN 12
#define SSP_SP_PROTOCOL_TDE 0X20
#define RETRYCOUNT 1
#define BSINTTOCHAR(x) (unsigned char)((x & 0xff000000)>>24), (unsigned char)((x & 0x00ff0000)>>16),(unsigned char)((x & 0x0000ff00)>>8),(unsigned char)(x & 0x000000ff)
using namespace std;
void byteswap(unsigned char* array,int size,int value);
bool moveTape(std::string tapeDevice,int count,bool dirForward);
void outputSense(SCSI_PAGE_SENSE* sd);
void readIOError(int err);
bool SCSIExecute(string tapedevice, unsigned char* cmd_p,int cmd_len,unsigned char* dxfer_p,int dxfer_len, bool cmd_to_device, bool show_error);
typedef struct { //structure for setting data encryption
unsigned char pageCode [2];
unsigned char length [2];
#if STENC_BIG_ENDIAN == 1
unsigned char scope :3;
unsigned char res_bits_1 :4;
unsigned char lock :1;
#else
unsigned char lock :1;
unsigned char res_bits_1 :4;
unsigned char scope :3;
#endif
#if STENC_BIG_ENDIAN == 1
unsigned char CEEM :2;
unsigned char RDMC :2;
unsigned char sdk :1;
unsigned char ckod :1;
unsigned char ckorp :1;
unsigned char ckorl :1;
#else
unsigned char ckorl :1;
unsigned char ckorp :1;
unsigned char ckod :1;
unsigned char sdk :1;
unsigned char RDMC :2;
unsigned char CEEM :2;
#endif
unsigned char encryptionMode;
unsigned char decryptionMode;
unsigned char algorithmIndex;
unsigned char keyFormat;
unsigned char res_bits_2 [8];
unsigned char keyLength [2];
unsigned char keyData [SSP_KEY_LENGTH];
} SSP_PAGE_SDE;
unsigned char
scsi_sense_command[6]={
0x03,
0,0,0,
sizeof(SCSI_PAGE_SENSE),
0
},
scsi_inq_command[6] = {
0x12,
0,0,0,
sizeof(SCSI_PAGE_INQ),
0
},
spin_des_command [SSP_SP_CMD_LEN] = {
SSP_SPIN_OPCODE,
SSP_SP_PROTOCOL_TDE,
0,
0X20,
0,0,
BSINTTOCHAR(sizeof(SSP_PAGE_BUFFER)),
0,0
},
spin_nbes_command [SSP_SP_CMD_LEN] = {
SSP_SPIN_OPCODE,
SSP_SP_PROTOCOL_TDE,
0,
0X21,
0,0,
BSINTTOCHAR(sizeof(SSP_PAGE_BUFFER)),
0,0
};
//Gets encryption options on the tape drive
SSP_DES* SSPGetDES(string tapeDevice){
SSP_PAGE_BUFFER buffer;
memset(&buffer,0,sizeof(SSP_PAGE_BUFFER));
if(!SCSIExecute(tapeDevice,
(unsigned char*)&spin_des_command,
sizeof(spin_des_command),
(unsigned char*)&buffer,
sizeof(SSP_PAGE_BUFFER),
false,true))
{
return NULL;
}
SSP_DES* status=new SSP_DES(&buffer);
return status;
}
//Gets encryption options on the tape drive
SSP_NBES* SSPGetNBES(string tapeDevice,bool retry){
SSP_PAGE_BUFFER buffer;
memset(&buffer,0,sizeof(SSP_PAGE_BUFFER));
if(!SCSIExecute(tapeDevice,
(unsigned char*)&spin_nbes_command,
sizeof(spin_nbes_command),
(unsigned char*)&buffer,
sizeof(SSP_PAGE_BUFFER),
false,false))
{
return NULL;
}
SSP_NBES* status=new SSP_NBES(&buffer);
if(status->nbes.encryptionStatus==0x01 && retry){
//move to the start of the tape and try again
int moves=0;
while(true){
if(status==NULL)break;
if(status->nbes.encryptionStatus!=0x01)break;
if(moves>=MAX_TAPE_READ_BLOCKS)break;
delete status;
status=NULL; //double free bug fix provided by Adam Nielsen
if(!moveTape(tapeDevice,1,true))break;
moves++;
status=SSPGetNBES(tapeDevice,false);
}
moveTape(tapeDevice,moves,false);
}
return status;
}
//Sends and inquiry to the device
SCSI_PAGE_INQ* SCSIGetInquiry(string tapeDevice){
SCSI_PAGE_INQ* status=new SCSI_PAGE_INQ;
memset(status,0,sizeof(SCSI_PAGE_INQ));
if(!SCSIExecute(tapeDevice,
(unsigned char*)&scsi_inq_command,
sizeof(scsi_inq_command),
(unsigned char*)status,
sizeof(SCSI_PAGE_INQ),
false,true))
{
exit(EXIT_FAILURE);
}
return status;
}
//Writes encryption options to the tape drive
bool SCSIWriteEncryptOptions(string tapeDevice, SCSIEncryptOptions* eOptions){
char buffer[1024];
memset(&buffer,0,1024);
SSP_PAGE_SDE options;
//copy the template over the options
memset(&options,0, sizeof(SSP_PAGE_SDE));
byteswap((unsigned char*)&options.pageCode,2,0x10);
int pagelen=sizeof(SSP_PAGE_SDE);
options.scope=2; //all IT nexus = 10b
options.RDMC=eOptions->rdmc;
options.ckod=(eOptions->CKOD)?1:0;
options.CEEM=DEFAULT_CEEM;
options.algorithmIndex=eOptions->algorithmIndex;
//set the specific options
switch(eOptions->cryptMode){
case CRYPTMODE_ON: //encrypt, read only encrypted data
options.encryptionMode=2;
options.decryptionMode=2;
break;
case CRYPTMODE_MIXED: //encrypt, read all data
options.encryptionMode=2;
options.decryptionMode=3;
break;
case CRYPTMODE_RAWREAD:
options.encryptionMode=2;
options.decryptionMode=1;
break;
default:
byteswap((unsigned char*)options.keyLength,2,DEFAULT_KEYSIZE);
eOptions->cryptoKey=""; //blank the key
eOptions->keyName=""; //blank the key name, not supported when turned off
break;
}
if(eOptions->cryptoKey!=""){
//byte swap the keylength
byteswap((unsigned char*)&options.keyLength,2,eOptions->cryptoKey.size());
//copy the crypto key into the options
eOptions->cryptoKey.copy((char*)&options.keyData, eOptions->cryptoKey.size(),0);
}
//create the key descriptor
if(eOptions->keyName!=""){
SSP_KAD kad;
memset(&kad,0,sizeof(kad));
kad.type=0x00;
kad.authenticated=0;
//set the descriptor length to the length of the keyName
byteswap((unsigned char*)&kad.descriptorLength,2,eOptions->keyName.size());
//get the size of the kad object
int kadlen=eOptions->keyName.size()+SSP_KAD_HEAD_LENGTH;
//increment the SPOUT page len
pagelen+=kadlen;
//increase the page size
eOptions->keyName.copy((char*)&kad.descriptor,eOptions->keyName.size(),0);
//copy the kad after the SDE command
memcpy(&buffer[sizeof(SSP_PAGE_SDE)],&kad,kadlen);
}
//update the pagelen in options
byteswap((unsigned char*)&options.length,2,pagelen-4); //set the page length, minus the length and pageCode
//copy the options to the beginning of the buffer
memcpy(&buffer,&options,sizeof(SSP_PAGE_SDE));
unsigned char spout_sde_command [SSP_SP_CMD_LEN] = {
SSP_SPOUT_OPCODE,
SSP_SP_PROTOCOL_TDE,
0,
0X10,
0,0,
BSINTTOCHAR(pagelen),
0,0
};
//return whether or not the command executed
return SCSIExecute(
tapeDevice,
(unsigned char*)&spout_sde_command,
sizeof(spout_sde_command),
(unsigned char*)&buffer,
pagelen,
true,true
);
}
bool SCSIExecute(string tapedrive, unsigned char* cmd_p,int cmd_len,unsigned char* dxfer_p,int dxfer_len, bool cmd_to_device, bool show_error)
{
const char* tapedevice=tapedrive.c_str();
int sg_fd,eresult,sresult,ioerr,retries;
SCSI_PAGE_SENSE* sd=new SCSI_PAGE_SENSE;
memset(sd,0,sizeof(SCSI_PAGE_SENSE));
#if defined(OS_LINUX) || defined(OS_FREEBSD) // Linux or FreeBSD System
errno=0;
sg_fd = open(tapedevice, O_RDONLY);
if( sg_fd==-1){
cerr<<"Could not open device '"<<tapedevice<<"': ";
readIOError(errno);
exit(EXIT_FAILURE);
}
sg_io_hdr cmdio;
memset(&cmdio,0,sizeof(sg_io_hdr));
cmdio.cmd_len = cmd_len;
cmdio.dxfer_direction =(cmd_to_device)?SG_DXFER_TO_DEV:SG_DXFER_FROM_DEV;
cmdio.dxfer_len = dxfer_len;
cmdio.dxferp = dxfer_p;
cmdio.cmdp = cmd_p;
cmdio.sbp = (unsigned char*)sd;
cmdio.mx_sb_len=sizeof(SCSI_PAGE_SENSE);
cmdio.timeout = SCSI_TIMEOUT;
cmdio.interface_id = 'S';
retries=0;
do{
errno=0;
eresult=ioctl(sg_fd, SG_IO, &cmdio);
if(eresult!=0)
ioerr=errno;
retries++;
}while(errno!=0 && retries<=RETRYCOUNT);
sresult=cmdio.status;
#elif defined(OS_AIX) // AIX System
errno=0;
sg_fd = openx((char*)tapedevice, O_RDONLY , NULL, SC_DIAGNOSTIC);
if(!sg_fd || sg_fd==-1){
cerr<<"Could not open device '"<<tapedevice<<"'"<<endl;
exit(EXIT_FAILURE);
}
struct sc_iocmd cmdio;
memset(&cmdio,0,sizeof (struct sc_iocmd));
//copy the command bytes into the first part of the structure
memcpy(&cmdio.scsi_cdb,cmd_p,cmd_len);
cmdio.buffer=(char*)dxfer_p;
cmdio.timeout_value=SCSI_TIMEOUT;
cmdio.command_length=cmd_len;
cmdio.data_length=dxfer_len;
cmdio.status_validity=SC_SCSI_ERROR;
cmdio.flags=(cmd_to_device)?B_WRITE:B_READ;
retries=0;
do{
errno=0;
eresult=ioctl(sg_fd, STIOCMD, &cmdio);
sresult=(int)cmdio.scsi_bus_status;
if(eresult!=0)
ioerr=errno;
retries++;
}while(errno!=0 && retries<=RETRYCOUNT);
if(sresult==SC_CHECK_CONDITION){ //get the sense data
struct sc_iocmd scmdio;
memset(&scmdio,0,sizeof (struct sc_iocmd));
//copy the command bytes into the first part of the structure
memcpy(&scmdio.scsi_cdb,&scsi_sense_command,sizeof(scsi_sense_command));
scmdio.buffer=(char*)sd;
scmdio.timeout_value=SCSI_TIMEOUT;
scmdio.command_length=sizeof(scsi_sense_command);
scmdio.data_length=sizeof(SCSI_PAGE_SENSE);
scmdio.status_validity=SC_SCSI_ERROR;
scmdio.flags=B_READ;
errno=0;
ioctl(sg_fd, STIOCMD, &scmdio);
}
#else
#error "OS type is not set"
#endif
#ifdef DEBUGSCSI
cout<<"SCSI Command: ";
for(int i=0;i<cmd_len;i++){
cout<<HEX(cmd_p[i]);
}
cout<<endl;
cout<<"SCSI Data: ";
for(int i=0;i<dxfer_len;i++){
cout<<HEX(dxfer_p[i]);
}
cout<<endl;
#endif
close(sg_fd);
bool retval=true;
if(eresult!=0){
if(show_error)
readIOError(ioerr);
retval=false;
}
if(sresult!=0){
if(show_error)
outputSense(sd);
retval=false;
}
delete sd;
return retval;
}
void byteswap(unsigned char* array,int size,int value){
switch(size){
case 2:
array[0]=(unsigned char)((value & 0xff00)>>8);
array[1]=(unsigned char)(value & 0x00ff);
break;
case 4:
array[0]=(unsigned char)((value & 0xff000000)>>24);
array[1]=(unsigned char)((value & 0x00ff0000)>>16);
array[2]=(unsigned char)((value & 0x0000ff00)>>8);
array[3]=(unsigned char)(value & 0x000000ff);
break;
default:
cout<<"Unhandled byte swap length of "<<size<<endl;
break;
}
}
SCSIEncryptOptions::SCSIEncryptOptions(){
cryptMode=CRYPTMODE_OFF;
algorithmIndex=DEFAULT_ALGORITHM;
cryptoKey="";
CKOD=false;
keyName="";
rdmc=RDMC_DEFAULT;
}
SSP_NBES::SSP_NBES(SSP_PAGE_BUFFER* buffer){
memset(&nbes,0,sizeof(SSP_PAGE_NBES));
memcpy(&nbes,buffer,sizeof(SSP_PAGE_NBES));
loadKADs(buffer,sizeof(SSP_PAGE_NBES));
}
SSP_DES::SSP_DES(SSP_PAGE_BUFFER* buffer){
memset(&des,0,sizeof(SSP_PAGE_DES));
memcpy(&des,buffer,sizeof(SSP_PAGE_DES));
loadKADs(buffer,sizeof(SSP_PAGE_DES));
}
void KAD_CLASS::loadKADs(SSP_PAGE_BUFFER* buffer, int start){
char* rawbuff=(char*)buffer;
int length=BSSHORT(buffer->length)+4;
int pos=start;
while(pos<length){
SSP_KAD kad;
memset(&kad,0,sizeof(SSP_KAD));
memcpy(&kad,rawbuff+pos,SSP_KAD_HEAD_LENGTH);
pos+=SSP_KAD_HEAD_LENGTH;
if(pos>=length)break;
unsigned short kadDesLen=BSSHORT(kad.descriptorLength);
if(kadDesLen>0){
memcpy(&kad.descriptor,rawbuff+pos,kadDesLen);
pos+=kadDesLen;
}else pos++;
kads.push_back(kad);
}
}
bool moveTape(std::string tapeDevice,int count,bool dirForward){
struct mtop mt_command;
int sg_fd = open(tapeDevice.c_str(), O_RDONLY);
if(!sg_fd || sg_fd==-1){
return false;
}
errno=0;
bool retval=true;
#if defined(OS_LINUX) || defined(OS_FREEBSD) // Linux or FreeBSD System
mt_command.mt_op = (dirForward)?MTFSR:MTBSR;
mt_command.mt_count = count;
ioctl(sg_fd, MTIOCTOP, &mt_command);
#elif defined(OS_AIX)
mt_command.st_op = (dirForward)?MTFSR:MTBSR;
mt_command.st_count = count;
ioctl(sg_fd, STIOCTOP, &mt_command);
#else
#error "OS type is not set"
#endif
if(errno!=0)retval=false;
close(sg_fd);
errno=0;
return retval;
}
void readIOError(int err){
if(err==0)return;
cerr<<"ERROR: ";
switch(err){
case EAGAIN:
cerr<<"Device already open"<<endl;
break;
case EBUSY:
cerr<<"Device Busy"<<endl;
break;
case ETIMEDOUT:
cerr<<"Device operation timed out"<<endl;
break;
case EIO:
cerr<<"Device I/O Error."<<endl;
break;
case EPERM:
cerr<<"You do not have privileges to do this. Are you root?"<<endl;
break;
#ifdef OS_AIX
case EBADF:
cerr<<"EBADF"<<endl;
break;
case EFAULT:
cerr<<"EFAULT"<<endl;
break;
case EINTR:
cerr<<"EINTR"<<endl;
break;
case EINVAL:
cerr<<"Invalid device"<<endl;
break;
case ENOTTY:
cerr<<"ENOTTY"<<endl;
break;
case ENODEV:
cerr<<"Device is not responding"<<endl;
break;
case ENXIO:
cerr<<"ENXIO"<<endl;
break;
#endif
default:
if(errno!=0){
cerr<<"0x"<<hex<<errno<<" "<<strerror(errno)<<endl;
}
}
}
void outputSense(SCSI_PAGE_SENSE* sd){
cerr<<left<<setw(25)<<"Sense Code: ";
switch((int)sd->senseKey){
case 0:
cerr<<"No specific error";
break;
case 2:
cerr<<"Device not ready";
break;
case 3:
cerr<<"Medium Error";
break;
case 4:
cerr<<"Hardware Error";
break;
case 5:
cerr<<"Illegal Request";
break;
case 6:
cerr<<"Unit Attention";
break;
case 7:
cerr<<"Data protect";
break;
case 8:
cerr<<"Blank tape";
break;
}
cerr<<" (0x"<<HEX(sd->senseKey)<<")"<<endl;
cerr<<left<<setw(25)<<" ASC:"<<"0x"<<HEX(sd->addSenseCode)<<endl;
cerr<<left<<setw(25)<<" ASCQ:"<<"0x"<<HEX(sd->addSenseCodeQual)<<endl;
if(sd->addSenseLen>0){
cerr<<left<<setw(25)<<" Additional data:"<<"0x";
for(int i=0;i<sd->addSenseLen;i++){
cerr<<HEX(sd->addSenseData[i]);
}
cerr<<endl;
}
#ifdef DEBUGSCSI
cerr<<left<<setw(25)<<" Raw Sense:"<<"0x";
char* rawsense=(char*)sd;
for(int i=0;i<sizeof(SCSI_PAGE_SENSE);i++){
cerr<<HEX(rawsense[i]);
}
cerr<<endl;
#endif
}