Files
s390-tools/tape390/tape390_crypt.c
Michael Holzheu b627b8d8e1 Initial s390-tools-2.0.0 import
This commit is based on the s390-tools-1.39.0 version.

Changes on top of s390-tools-1.39.0:

 - Add MIT license to all source files
 - Add LICENSE file
 - Transform REAMDE to README.md (markdown)
 - Add AUTHORS.md file
 - Add CONTRIBUTING.md file
 - Move changelog from README to CHANGELOG.md file

Reviewed-by: Stefan Haberland <sth@linux.vnet.ibm.com>
Signed-off-by: Michael Holzheu <holzheu@linux.vnet.ibm.com>
2017-08-21 10:55:40 +02:00

582 lines
15 KiB
C

/*
* tape390_crypt: Provide encryption for tape devices
*
* Copyright 2006, 2017 IBM Corp.
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mtio.h>
#include <unistd.h>
#include "lib/zt_common.h"
#include "tape390_common.h"
#define KEKL_LENGTH 65 /* 64 bytes plus trailing \0 */
#define TOT_KEKL_LENGTH KEKL_LENGTH+1+5 /* kekl plus colon plus LABEL or HASH */
#define ENCRYPTION_ON 1
#define ENCRYPTION_OFF 0
#define CRYPT_SET_FAILED_MSG "%s: Tape drive does not support encryption.\n"
#define CRYPT_QUERY_FAILED_MSG CRYPT_SET_FAILED_MSG
#define KEKL_QUERY_FAILED_MSG "%s: Query of encryption keys failed.\n"
#define KEKL_SET_FAILED_MSG "%s: Unable to set or modify encryption keys.\n"
#define PROC_DEVICES_FILE "/proc/devices"
#define PROC_DEVICES_FILE_WIDTH 100
#define MAX_BLOCKSIZE 65535 /* max blocksize supported by escon/ficon tapes */
#ifndef ENOKEY
#define ENOKEY 126
#endif
#ifndef EKEYREJECTED
#define EKEYREJECTED 129
#endif
#define CHECK_SPEC_MAX(i,j,numstr,str) \
{if (i>j) ERRMSG_EXIT(EXIT_MISUSE,"%s: " str " " \
"can only be specified " numstr ".\n",prog_name);}
#define PRINT_TYPE(byte) \
{if (byte==TAPE390_KEKL_TYPE_LABEL) printf("label\n");\
else if (byte == TAPE390_KEKL_TYPE_HASH) printf("hash\n");\
else printf("Unsupported type\n");}
/*
* Structure tape390_crypt is used to store the command line parameters
*/
struct tape390_crypt {
int e_switch;
char delimiter;
char devname[PATH_MAX];
char kekl_input[2][TOT_KEKL_LENGTH];
int encryption_specified;
int query_specified;
int key_specified;
int delimiter_specified;
int force_specified;
} __attribute__ ((packed));
/*
* The following ioctl's belong to linux-2.5/include/asm-s390/tape390.h
*
* The TAPE390_CRYPT_SET ioctl is used to switch on/off encryption.
* The "encryption_capable" field is ignored for this ioctl!
*/
#define TAPE390_CRYPT_SET _IOW('d', 2, struct tape390_crypt_info)
/*
* The TAPE390_CRYPT_QUERY ioctl is used to query the encryption state.
*/
#define TAPE390_CRYPT_QUERY _IOR('d', 3, struct tape390_crypt_info)
struct tape390_crypt_info {
char capability;
char status;
char medium_status;
} __attribute__ ((packed));
/* Macros for "capable" field */
#define TAPE390_CRYPT_SUPPORTED_MASK 0x01
#define TAPE390_CRYPT_SUPPORTED(x) \
((x.capability & TAPE390_CRYPT_SUPPORTED_MASK))
/* Macros for "status" field */
#define TAPE390_CRYPT_ON_MASK 0x01
#define TAPE390_CRYPT_ON(x) (((x.status) & TAPE390_CRYPT_ON_MASK))
/* Macros for "medium status" field */
#define TAPE390_MEDIUM_LOADED_MASK 0x01
#define TAPE390_MEDIUM_ENCRYPTED_MASK 0x02
#define TAPE390_MEDIUM_ENCRYPTED(x) \
(((x.medium_status) & TAPE390_MEDIUM_ENCRYPTED_MASK))
#define TAPE390_MEDIUM_LOADED(x) \
(((x.medium_status) & TAPE390_MEDIUM_LOADED_MASK))
/*
* The TAPE390_KEKL_SET ioctl is used to set Key Encrypting Key labels.
*/
#define TAPE390_KEKL_SET _IOW('d', 4, struct tape390_kekl_pair)
/*
* The TAPE390_KEKL_QUERY ioctl is used to query Key Encrypting Key labels.
*/
#define TAPE390_KEKL_QUERY _IOR('d', 5, struct tape390_kekl_pair)
struct tape390_kekl {
unsigned char type;
unsigned char type_on_tape;
char label[65];
} __attribute__ ((packed));
struct tape390_kekl_pair {
struct tape390_kekl kekl[2];
} __attribute__ ((packed));
/* Values for "kekl1/2_type" and "kekl1/2_type_on_tape" fields */
#define TAPE390_KEKL_TYPE_NONE 0
#define TAPE390_KEKL_TYPE_LABEL 1
#define TAPE390_KEKL_TYPE_HASH 2
/* Full tool name */
static const char tool_name[] = "tape390_crpyt: zSeries tape encryption program";
/* Copyright notice */
static const char copyright_notice[] = "Copyright IBM Corp. 2006, 2017";
/*
* Print version information.
*/
static void print_version (void)
{
printf ("%s version %s\n", tool_name, RELEASE_STRING);
printf ("%s\n", copyright_notice);
}
/*
* prints out the usage text
*/
static void tape390_crypt_usage (void)
{
printf ("Usage: tape390_crypt [OPTIONS] [DEVICE]\n"
"\n"
"Provide encryption for "
"tape devices.\n"
"DEVICE is the node of the tape device (e.g. '/dev/ntibm0')\n"
"\n"
"-h, --help Print this help, then exit\n"
"-v, --version Print version information, "
"then exit\n"
"-q, --query Query information on encryption "
"status and keys\n"
"-k, --key value:type Set encryption key(s)\n"
"-d, --delimiter char Specify character separating "
"key value from key type\n"
"-f, --force Do not prompt user\n"
"-e, --encryption on|off Switch encryption on or off\n");
}
/*
* initialize the tape390_crypt info structure
*/
static void init_info(struct tape390_crypt *info)
{
info->delimiter = ':';
info->encryption_specified = 0;
info->query_specified = 0;
info->key_specified = 0;
info->delimiter_specified = 0;
info->force_specified = 0;
}
/*
* parse the commandline options
*/
static void tape390_crypt_parse_opts(struct tape390_crypt *info, int argc,
char* argv[])
{
int opt, index, kekl_too_long=0, deli_too_long=0, e_switch_invalid=0;
int i;
static struct option crypt_long_options[] = {
{ "version", no_argument, NULL, 'v'},
{ "encryption", required_argument, NULL, 'e'},
{ "query", no_argument, NULL, 'q'},
{ "key", required_argument, NULL, 'k'},
{ "help", no_argument, NULL, 'h'},
{ "delimiter", required_argument, NULL, 'd'},
{ "force", no_argument, NULL, 'f'},
{ NULL, 0, NULL, 0 }
};
/* Command line option abbreviations */
static const char crypt_option_string[] = "ve:qk:hd:f";
while (1) {
opt = getopt_long(argc, argv, crypt_option_string,
crypt_long_options, &index);
if (opt == -1)
break;
switch (opt) {
case 'v':
print_version();
exit(0);
case 'h':
tape390_crypt_usage();
exit(0);
case 'q':
++info->query_specified;
break;
case 'k':
++info->key_specified;
if(strlen(optarg) >= TOT_KEKL_LENGTH)
kekl_too_long = 1;
else if (info->key_specified == 1)
for (i = 0; i < 2; i++)
strcpy(info->kekl_input[i], optarg);
else
strcpy(info->kekl_input[1], optarg);
break;
case 'e':
++info->encryption_specified;
if (strcmp(optarg,"off") == 0)
info->e_switch = ENCRYPTION_OFF;
else if (strcmp(optarg,"on") == 0)
info->e_switch = ENCRYPTION_ON;
else
e_switch_invalid = 1;
break;
case 'd':
++info->delimiter_specified;
if(strlen(optarg) > 1)
deli_too_long = 1;
else
info->delimiter = optarg[0];
break;
case 'f':
++info->force_specified;
break;
default:
fprintf(stderr, "Try '%s --help' for more"
" information.\n",prog_name);
exit(1);
}
}
CHECK_SPEC_MAX(info->query_specified, 1, "once", "query");
CHECK_SPEC_MAX(info->encryption_specified, 1, "once", "encryption");
CHECK_SPEC_MAX(info->delimiter_specified, 1, "once", "delimiter");
CHECK_SPEC_MAX(info->force_specified, 1, "once", "force");
CHECK_SPEC_MAX(info->key_specified,2, "twice", "key");
if (kekl_too_long)
ERRMSG_EXIT(EXIT_MISUSE,
"%s: Specified key too long.\n",prog_name);
if (deli_too_long)
ERRMSG_EXIT(EXIT_MISUSE,
"%s: Delimiter must be one character.\n",prog_name);
if (e_switch_invalid)
ERRMSG_EXIT(EXIT_MISUSE,
"%s: ERROR: encryption can be set 'on' or 'off'.\n",
prog_name);
if (((info->query_specified) &&
(info->key_specified + info->encryption_specified > 0)) ||
((info->encryption_specified) &&
(info->key_specified + info->query_specified > 0)) ||
((info->force_specified) &&
(info->encryption_specified)) ||
((info->delimiter_specified) &&
(info->encryption_specified + info->query_specified > 0)))
ERRMSG_EXIT(EXIT_MISUSE,
"%s: Conflicting options specified.\n",prog_name);
if (info->query_specified + info->encryption_specified +
info->key_specified == 0)
ERRMSG_EXIT(EXIT_MISUSE,
"%s: ERROR: Missing options.\n",prog_name);
/* save device */
if (optind >= argc)
ERRMSG_EXIT(EXIT_MISUSE,
"%s: No device specified.\n",prog_name);
if (optind + 1 < argc)
ERRMSG_EXIT(EXIT_MISUSE,
"%s: More than one device speficied.\n",prog_name);
strcpy(info->devname, argv[optind]);
}
/*
* check and parse key operand, must be value:type
* where value is an arbitrary string, maximal 64 char long
* : is a 1-char delimiter specified with the -d option (default :)
* type is the type identifier, which can be either 'hash' or 'label'
* default is 'label'
*/
static void fill_kekl(char *s, char delimiter,
struct tape390_kekl *mykekl)
{
int i;
char typestring[TOT_KEKL_LENGTH] = {0};
for (i = strlen(s); i >= 0; i--)
if (*(s+i) == delimiter)
break;
if (i == -1) /* delimiter not found, no type specified */
mykekl->type_on_tape = TAPE390_KEKL_TYPE_LABEL; /* default type*/
else if (i == 0)
ERRMSG_EXIT(EXIT_MISUSE,
"%s: ERROR: delimiter '%c' found as first "
"character.\n",prog_name,delimiter);
else if (i == (int) strlen(s) - 1)
ERRMSG_EXIT(EXIT_MISUSE,
"%s: ERROR: Delimiter '%c' found as last "
"character.\n",prog_name,delimiter);
else {
strcpy(typestring,s+i+1);
*(s+i) = '\0';
if (strcmp(typestring,"hash") == 0)
mykekl->type_on_tape = TAPE390_KEKL_TYPE_HASH;
else if (strcmp(typestring,"label") == 0)
mykekl->type_on_tape = TAPE390_KEKL_TYPE_LABEL;
else
ERRMSG_EXIT(EXIT_MISUSE,
"%s: Invalid key type '%s',"
" must be either 'label' or 'hash'.\n",
prog_name,typestring);
}
if (strlen(s) >= KEKL_LENGTH)
ERRMSG_EXIT(EXIT_MISUSE,
"%s: Specified key value too long, has %d chars.\n",
prog_name,(int)strlen(s));
else {
strcpy(mykekl->label, s);
mykekl->type = TAPE390_KEKL_TYPE_LABEL;
}
}
/*
* Write two tapemarks on tape to initialize tape after kekl set
*/
static int init_tape(int fd)
{
struct mtop mtop;
int rc;
mtop.mt_op = MTWEOF;
mtop.mt_count = 2;
rc = ioctl(fd, MTIOCTOP, &mtop);
if (rc)
return rc;
mtop.mt_op = MTREW;
return ioctl(fd, MTIOCTOP, &mtop);
}
/*
* Tell the current block on tape
*/
static int tell_tape(int fd)
{
struct mtop mtop;
mtop.mt_op = MTTELL;
mtop.mt_count = 1;
return ioctl(fd, MTIOCTOP, &mtop);
}
/*
* Do read IO to tape in order to enforce update of KEKL information.
* Leave tape position unchanged.
*/
static int read_tape(int fd)
{
struct mtop mtop;
int block_number, n_read;
char buffer[MAX_BLOCKSIZE];
/* remember current block on tape */
block_number = tell_tape(fd);
n_read = read(fd, buffer, sizeof(buffer));
if (n_read == -1) {
/* tape read failed, ignore if caused by end-of-volume */
if (errno == ENOSPC)
return 0;
else
return 1;
}
if (block_number != tell_tape(fd)) {
/* re-position tape */
mtop.mt_op = MTSEEK;
mtop.mt_count = block_number;
if (ioctl(fd, MTIOCTOP, &mtop) != 0)
return 1;
}
return 0;
}
/*
* set Key Encrypting Key labels
*/
static int set_encryption_keys(int fd, struct tape390_crypt *info)
{
struct tape390_kekl_pair kekls;
char delim;
char *s, inp_buffer[5];
int i, rc;
delim = info->delimiter;
for (i = 0; i < 2; i++) {
s = info->kekl_input[i];
fill_kekl(s, delim, &kekls.kekl[i]);
}
if (info->force_specified == 0) {
printf("--->> ATTENTION! <<---\n");
printf("All data on tape %s will be lost.\nType "
"\"yes\" to continue: ", info->devname);
if (fgets(inp_buffer, sizeof(inp_buffer), stdin) == NULL) {
fprintf(stderr, "Reading from terminal failed.\n");
exit(1);
}
if (strcasecmp(inp_buffer,"yes\n")) {
printf("Tape and its encryption keys "
"remain unchanged.\n");
exit(0);
}
}
rc = ioctl(fd, TAPE390_KEKL_SET, &kekls);
if (rc)
return rc;
return init_tape(fd);
}
/*
* Switch on/off encryption
*/
static int switch_encryption_on_or_off(int fd,struct tape390_crypt *info)
{
struct tape390_crypt_info myinfo;
memset(&myinfo, 0, sizeof(myinfo));
if (info->e_switch == ENCRYPTION_ON)
myinfo.status |= TAPE390_CRYPT_ON_MASK;
return ioctl(fd, TAPE390_CRYPT_SET, &myinfo);
}
/*
* Query information on encryption and keys
*/
static int query_encryption(int fd,struct tape390_crypt *info)
{
struct tape390_crypt_info myinfo;
struct tape390_kekl_pair kekls;
int i, ret;
ret = ioctl(fd, TAPE390_CRYPT_QUERY, &myinfo);
if (ret)
return -1;
if (!TAPE390_MEDIUM_LOADED(myinfo)) {
printf("Medium not loaded\n");
return 0;
}
printf("ENCRYPTION: ");
if (TAPE390_CRYPT_ON(myinfo))
printf("ON\n");
else
printf("OFF\n");
printf("MEDIUM: ");
if (TAPE390_MEDIUM_ENCRYPTED(myinfo))
printf("ENCRYPTED\n");
else {
printf("NOT ENCRYPTED\n");
return 0;
}
/*
* do a dummy read in order to force the control unit to get
* the key information from the EKM
*/
ret = read_tape(fd);
if (ret && (info->force_specified == 0))
return -2;
ret = ioctl(fd, TAPE390_KEKL_QUERY, &kekls);
if (ret)
return -2;
printf("KEKLs on TAPE:\n");
if (kekls.kekl[0].type == TAPE390_KEKL_TYPE_NONE) {
ERRMSG("Unable to retrieve key information.\n");
return 0;
}
for (i = 0; i < 2; i++) {
printf(" KEY%i:\n",i+1);
if (kekls.kekl[i].type == TAPE390_KEKL_TYPE_NONE)
printf("None\n");
else {
printf(" value: %s\n",kekls.kekl[i].label);
printf(" type: ");
PRINT_TYPE(kekls.kekl[i].type);
printf(" ontape: ");
PRINT_TYPE(kekls.kekl[i].type_on_tape);
}
}
return 0;
}
int main(int argc, char *argv[])
{
struct tape390_crypt info;
char error_msg[80];
int rc=0, fd=-1;
/* set name of program */
set_prog_name(basename(argv[0]));
/* set default values */
init_info(&info);
/* parse command line options and check syntax */
tape390_crypt_parse_opts(&info, argc, argv);
/* check whether specified device node is tape */
if (is_not_tape(info.devname))
exit(EXIT_MISUSE);
/* open device */
fd = open_tape(info.devname);
/* process -k or -e or -q operand */
if (info.key_specified >= 1) {
rc = set_encryption_keys(fd, &info);
if (rc == 0)
printf("SUCCESS: key information set.\n");
strcpy(error_msg, KEKL_SET_FAILED_MSG);
} else if (info.encryption_specified) {
rc = switch_encryption_on_or_off(fd, &info);
strcpy(error_msg, CRYPT_SET_FAILED_MSG);
} else if (info.query_specified) {
rc = query_encryption(fd, &info);
if (rc == -1)
strcpy(error_msg, CRYPT_QUERY_FAILED_MSG);
else if (rc == -2)
strcpy(error_msg, KEKL_QUERY_FAILED_MSG);
}
if (rc) {
ERRMSG(error_msg, prog_name);
switch (errno) {
case ENOKEY:
ERRMSG("EKM error.\n");
break;
case ENOTCONN:
ERRMSG("Could not connect to EKM.\n");
break;
case EKEYREJECTED:
ERRMSG("Key not available on EKM.\n");
break;
case EUNATCH:
ERRMSG("Encryption must be set ON prior to "
"setting or querying encryption "
"keys.\n");
break;
case EBADSLT:
ERRMSG("Tape must be at load point in order "
"to set encryption keys.\n");
break;
case ENOSYS:
ERRMSG("Tape device does not support "
"encryption.\n");
break;
case EINVAL:
ERRMSG("Kernel does not support tape "
"encryption.\n");
break;
default:
perror("");
}
}
if (fd != -1)
close(fd);
return(rc);
}