Sample Code–Roil
The following two source files comprise the Roil codebase. To preserve readability, we richly comment the code but do not include any book-text inside the code. You can download the full source files from this book's companion Web site at http://www.wiley.com/compbooks/schiffman.
roil.h
/*
* $Id: roil.h,v 1.1 2002/04/11 04:42:06 route Exp $
*
* Building Open Source Network Security Tools
* roil.h - openssl example code
*
* Copyright (c) 2002 Mike D. Schiffman <mike@infonexus.com>
* All rights reserved.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "/usr/local/ssl/include/openssl/evp.h"
#define KEY_LENGTH 0x100 /* max passphrase size */
#define IV_LENGTH 0x008 /* IV length */
#define RETRY_THRESHOLD 0x003 /* password retries */
#define BUF_SIZE 0x100 /* 256 byte buffer */
# define ERRBUF_SIZE 0x100 /* 256 byte buffer */
/* magic file header number */
u_char magic[]= {0x0f, 0x01, 0x02, 0x0d, 0xff, 0xee, 0xfl, 0x43};
struct roil_pack
{
int fd_in;
int fd_out;
char fn_in[100];
char fn_out[100];
char passphrase[KEY_LENGTH];
u_char flags;
#define MD 0x01 /* Hash */
#define MD_FROMFILE 0x02 /*Hash from a file */
#define ENCRYPT 0x04 /* Encrypt */
#define DECRYPT 0x08 /* Decrypt */
char md[10];
char ea[10];
char errbuf[ERRBUF_SIZE];
};
struct roil_pack *roil_init(char *, u_char, char *, char *, char *);
int open_outputfile(struct roil_pack *);
void roil_destroy(struct roil_pack *);
void roil(struct roil_pack *);
u_char *roil_digest(struct roil_pack *, int *);
int roil_cipher(struct roil_pack *);
int get_passphrase(char *);
int make_key(struct roil_pack *, u_char *);
void get_iv(u_char *);
void usage(char *);
/* EOF */
roil.c
/*
* $Id: roil.c,v 1.1 2002/04/11 04:42:06 route Exp $
*
* Building Open Source Network Security Tools
* roil.c - openssl example code
*
* Copyright (c) 2002 Mike D. Schiffman <mike@infonexus.com>
* All rights reserved.
*
#include "./roil.h"
int
main(int argc, char **argv)
{
int c;
u_char flags;
char *md;
char *ea;
FILE *filename;
char errbuf[256];
struct roil_pack *rp;
printf("Roil 1.0 [little encryption tool]\n");
flags = 0;
md = NULL;
ea = NULL;
filename = NULL;
while ((c = getopt(argc, argv, "de:hm:")) != EOF)
{
switch (c)
{
case 'd':
flags |= DECRYPT;
break;
case 'e':
ea = optarg;
flags |= ENCRYPT;
break;
case 'h':
usage(argv[0]};
exit(EXIT_SUCCESS);
break;
case 'm':
md = optarg;
flags |= MD;
break;
default:
usage(argv[0]);
exit(EXIT_FAILURE);
}
}
if (flags == 0 || (flags & ENCRYPT && flags & DECRYPT))
{
usage(argvf0]);
exit(EXIT_FAILURE);
}
if (argc - optind !=1)
{
usage(argv[0]);
exit(EXIT_FAILURE);
}
rp= roil_init(argv[optind], flags, md, ea, errbuf);
if (rp==NULL)
{
fprintf(stderr, "roil_init(): %s n" errbuf);
exit(EXIT_FAILURE)
}
roil(rp);
roil_destroy(rp);
return (EXIT_SUCCESS);
}
struct roil_pack *
roil_init(char *filename, u_char flags, char *md, char *ea, char *errbuf)
{
struct roil_pack *rp;
/* grab memory for our monolithic structure */
rp= malloc(sizeof (struct roil_pack));
if (rp==NULL)
{
sprintf(errbuf, strerror(errno));
return (NULL);
}
/* open the input file */
rp->fd_in= open(filename, 0_RDWR);
if (rp->fd_in==-1)
{
sprintf(errbuf, "can't open input file \"%s" %s",
filename, strerror(errno));
roil_destroy(rp);
return (NULL);
}
/* save the filename */
strncpy(rp-> fn_in, filename, sizeof (rp->fn_in) - 1);
rp->flags = flags;
/* copy over the message digest name */
if (md)
{
strncpy (rp->md, md, 10);
}
/* copy over the message digest name */
if (ea)
{
strncpy(rp->ea, ea, 10);
}
return (rp);
}
void
roil_destroy (struct roil_pack *rp
{
if (rp)
{
if (rp->fd_in)
{
close (rp->fd_in);
}
if (rp->fd_out)
{
close (rp->fd_out); }
}
free(rp);
EVP_cleanup();
}
}
int
open_outputfile(struct roil_pack *rp)
{
int n;
n= strlen(rp->fn_in);
strcpy(rp->fn_out, rp->fn_in);
if (rp->flags & ENCRYPT)
{
if (!(n + 4 100))
{
/* filename too long */
sprintf(rp->errbuf, "open_outputfile(): filename too long n");
return (-1);
}
strcpy(rp->fn_out + n, ".roil");
}
else
{
if (n < 4)
{
/* filename too short */
sprintf(rp->errbuf,
"open_outputfile(): filename too short n");
return (-1);
}
if (strncmp(&rp->fn_out[n - 5], ".roil", 5)==0)
{
/* cut ".roil" from filename */
rp->fn_out[n - 5]= 0;
}
else
{
/* unknown suffix / filename */
sprintf(rp->errbuf, "open_outputfile(): unknown suffix \n");
return (-1);
}
}
/* open the file */
rp->fd_out= open(rp->fn_out, 0_CREAT | 0_WRONLY);
if (rp->fd_out==-1)
{
sprintf(rp->errbuf, "open_outputfile(): %s n", strerror(errno));
return (-1);
}
/* set a umask of 600 */
if (fchmod(rp->fd_out, 0600)==-1)
{
sprintf(rp->errbuf, "open_outputflie(): %s\n", strerror(errno));
return (-1);
}
return (1);
}
void
roil(struct roil_pack *rp)
{
int n, len;
u_char *p;
if (rp->flags & MD)
{
/*
* We're going to be digesting a file here. The other case
* when we would be digesting a user's passphrase to create a
* sufficiently long key for encryption or decryption comes
* into play from within roil_cipher() and never here.
*/
rp->flags |=MD_FROMFILE;
/*
* Digest the file contained in rp. Upon success, the function
* will return a pointer to a static buffer containing the hash
* and the length will be written to len. Upon failure p will
* point to a NULL buffer and rp- errbuf will contain the
* reason.
*/
p = roil_digest(rp, &len);
if (p==NULL)
{
fprintf(stderr, "roil_digest(): %s", rp->errbuf);
return;
}
printf("%s message digest of %s: ", rp->md, rp->fn_in);
for (n = 0; n < len; n++)
{
printf ("%02x", p[n]);
}
printf("\n");
}
else if ((rp- flags & ENCRYPT) || (rp- flags & DECRYPT))
{
/*
* Encrypt or decrypt the file contained in rp. Upon success,
* the function will return 1; upon failure the function will
* return -1 and rp-> errbuf will contain the reason.
*/
if (roil_cipher(rp)==-1)
{
fprintf(stderr, "roil_cipher(): %s", rp->errbuf);
return;
}
}
}
u_char *
roil_digest(struct roil_pack *rp, int *digest_len)
{
int n;
const EVP_MD *md;
u_char buf[BUF_SIZE];
EVP_MD_CTX md_context;
static u_char digest[EVP_MAX_MD_SIZE];
/* add all available digest algorithms to the hash table */
OpenSSL_add_all_digests();
/* load and verify the digest specified at the command line */
md = EVP_get_digestbyname(rp->md);
if (md==NULL)
{
snprintf(rp->errbuf, ERRBUF_SIZE, "unknown digest %s n",rp->md);
goto bad;
}
/*
* Initialize the md context. Really all this does is zero out the
* structure.
*/
EVP_MD_CTX_init(&md_context);
/* initialize the md algorithm */
if (EVP_DigestInit(&md_context, md)==0)
{
snprintf(rp->errbuf, ERRBUF_SIZE, "EVP_DigestInit() failed \n");
goto bad;
}
memset (digest, 0, sizeof (digest));
if (rp->flags & MD_FROMFILE)
{
/*
* Digest the file. Read in a block of data into buf and
* process it with the md algorithm.
*/
while ((n= read(rp->fd_in, buf, sizeof (buf))) 0)
{
if (EVP_DigestUpdate(&md_context, buf, n)==0)
{
snprintf(rp->errbuf, ERRBUF_SIZE,
"EVP_DigestUpdate() failed \n");
goto bad;
}
}
/* retrieve the digest value and length from the md context */
if (EVP_DigestFinal(&md_context, digest, digest_len)==0)
{
snprintf(rp->errbuf, ERRBUF_SIZE,
"EVP_DigestFinal() failed \n");
goto bad;
}
}
else
{
/*
* Digest a user's passphrase. Since we know this no more
* than KEY_LENGTH bytes, we can do it all in one chunk.
*/
if (EVP_DigestUpdate(&md_context, rp- passphrase,
strlen(rp->passphrase))==0)
{
snprintf(rp- errbuf, ERRBUF_SIZE,
"EVP_DigestUpdate() failed \n");
goto bad;
}
if (EVP_DigestFinal(&md_context, digest, digest_len)==0)
{
snprintf(rp->errbuf, ERRBUF_SIZE,
"EVP_DigestFinal() failed \n");
goto bad;
}
}
return (digest);
bad:
* digest_len= 0;
return (NULL);
}
int
roil_cipher(struct roil_pack *rp)
{
int n, m, mode;
EVP_CIPHER_CTX ea_context;
const EVP_CIPHER *ea;
u_long bytecnt;
u_char buf[BUF_SIZE], ebuf[BUF_SIZE], key[KEY_LENGTH],
iv[IV_LENGTH];
/* set the mode for the cipher functions */
mode= (rp->flags & ENCRYPT) ? 1 : 0;
/* add all available encryption algorithms to the hash table */
OpenSSL_add_all_ciphers();
if (rp->flags & ENCRYPT)
{
/*
* If we're encrypting, we have to first load and verify the
* cipher specified at the command line.
*/
ea = EVP_get_cipherbyname(rp->ea);
if (ea == NULL)
{
snprintf(rp->errbuf, ERRBUF_SIZE, "unknown cipher %s n",
rp->ea);
return (-1);
}
}
else /* decrypting */
{
/*
* If we're decrypting, we have to check to see if this file
* was previously encrypted by roil. To do that, we read the
* first 8 bytes and see if they correspond to the "magic
* number" that is written to every roiled file prior to
* encryption.
*/
n= read(rp->fd_in, buf, 8);
if (n != 8)
{
snprintf(rp->errbuf, ERRBUF_SIZE, "read error %s\n",
strerror(errno));
return (-1);
}
if (bcmp(buf, magic, 8))
{
snprintf(rp->errbuf, ERRBUF_SIZE, "%s is not a roiled file n",
rp->fn_in);
return (-1);
}
/*
* Next, we have to determine which symmetric cipher was used
* to encrypt the file. That is written in the next 16 bytes
* of the file.
*/
n = read(rp->fd_in, buf, 16);
if (n !=16)
{
snprintf(rp->errbuf, ERRBUF_SIZE, "read error %s n",
strerror(errno));
return (-1);
}
/*
* Look up the cipher by canonical name and if it's "good" fill
* in an EVP_CIPHER structure.
*/
ea= EVP_get_cipherbyname(buf);
if (ea==NULL)
{
snprintf(rp->errbuf, ERRBUF_SIZE, "unknown cipher %s n",
buf) ;
return (-1);
}
/*
* The next 8 bytes contain the initialization vector, which
* may or may not be used by the algorithm. We store it either
* way.
*/
n = read(rp->fd_in, iv, 8);
if (n !=8)
{
snprintf(rp->errbuf, ERRBUF_SIZE, "read error %s n",
strerror(errno));
return (-1);
}
}
/*
* Get a passpnrase from the user to use as a key for the symmetric
* encryption.
*/
if (get_passphrase(rp->passphrase)==-1)
{
snprintf(rp->errbuf, ERRBUF_SIZE, "can't read passphrase %s n",
strerror(errno));
return (-1);
}
/*
* Take the passphrase and hash it using SHA1 to create our
* symmetric key.
*/
if (make_key(rp, key)==-1)
{
/* error set in roil_digest() */
return (-1);
}
/* we appear good to go; we open our output file */
if (open_outputfile(rp)==-1)
{
/* error set in open_outputfile() */
return (-1);
}
if (rp->flags & ENCRYPT)
{
/*
* Write out our 8 byte magic number to the file. This will
* let the decryption code know if this file was encrypted by
* us or not.
*/
n = write(rp->fd_out, magic, 8);
if (n != 8)
{
snprintf(rp->errbuf, ERRBUF_SIZE, "write error %s\n",
strerror (errno) );
return (-1);
}
/*
* Write the encryption algorithm to the file, which will be
* NULL padded to 16 bytes. This will allow the decryption
* code to figure it out without needing the user to specify.
*/
memset(buf, 0, sizeof (buf));
memcpy(buf, rp->ea, strlen(rp->ea)) ;
n= write(rp->fd_out, buf, 16);
if (n !=16)
{
snprintf(rp->errbuf, ERRBUF_SIZE, "write error %s n",
strerror(errno));
return (-1);
}
/*
* Some encryption algorithms use an initialization vector to
* seed the first round of encryption with (it acts as a dummy
* block). We might need it so we'll get one and write it to
* the file next.
*/
get_iv(iv);
n= write(rp->fd_out, iv, 8);
if (n != 8)
{
snprintf(rp->errbuf, ERRBUF_SIZE, "write error %s n",
strerror(errno));
return (-1);
}
}
/*
* Initialize the cipher context. Really all this does is zero
* out the structure.
*/
EVP_CIPHER_CTX_init(&ea_context);
/* initialize the encryption/decryption operation */
if (EVP_CipherInit_ex(&ea_context, ea, NULL, key, iv, mode)==0)
{
snprintf(rp->errbuf, ERRBUF_SIZE, "EVP_CipherInit_ex() failed\n");
return (-1);
}
/*
* Encrypt/decrypt the file. Read a block of data, encrypt it and
* write it out to the file.
*/
if (rp->flags & ENCRYPT)
{
fprintf(stderr, "\nencrypting file \"%s\"\n",
rp->fn_in);
}
else
{
fprintf(stderr, "\ndecrypting %s encrypted file\"%s "\n", buf,
rp->fn_in);
}
bytecnt= 0;
while ((n= read(rp->fd_in, buf, sizeof (buf))) > 0)
{
bytecnt +=n;
/*
* Encrypt or decrypt n bytes from buf and write the output to
* ebuf.
*/
if (EVP_CipherUpdate(&ea_context, ebuf, &m, buf, n)==0)
{
snprintf(rp->errbuf, ERRBUF_SIZE,
"EVP_CipherUpdate() failed \n");
return (-1);
}
n = write(rp->fd_out, ebuf, m);
if (n !=m)
{
snprintf(rp->errbuf, ERRBUF_SIZE, "write error %s n",
strerror(errno));
return (-1);
}
fprintf(stderr, "byte: Ox%081x\r", bytecnt);
}
/*
* Finalize the encryption or decryption by taking care of padding
* the last block if necessary.
*/
if (EVP_CipherFinal_ex(&ea_context, ebuf, &m)==0)
{
snprintf(rp->errbuf, ERRBUF_SIZE,
"EVP_CipherFinal_ex() failed \n");
return (-1);
}
n= write(rp- fd_out, ebuf, m);
if (n !=m)
{
snprintf(rp->errbuf, ERRBUF_SIZE, "write error %s n",
strerror(errno));
return (-1);
}
printf("\ndone, output file is \"%s \"\n", rp->fn_out);
return (1);
}
int
get_passphrase(char *passphrase)
{
int n, retry;
char passphrase_match[KEY_LENGTH];
struct termios term;
/* we want to turn off terminal echoing so no one can see! */
n= tcgetattr(STDIN_FILENO, &term);
if (n==-1)
{
fprintf(stderr, "warning: password will be echoed\n");
/* nonfatal */
}
else
{
/* disable terminal echo */
term.c_lflag &=-ECHO;
}
/* set our changed state "NOW"*/
n= tcsetattr(STDIN_FILENO, TCSANOW, &term);
if (n==-1)
{
fprintf(stderr, "warning: password will be echoed \n");
/* nonfatal */
}
retry= RETRY_THRESHOLD;
memset(passphrase, 0, KEY_LENGTH);
again:
printf("Passphrase: ");
if (fgets(passphrase, KEY_LENGTH, stdin)==NULL)
{
return (-1);
}
passphrase[strlen(passphrase) - 1]= 0;
printf("\nAgain:");
if (fgets(passphrase_match, KEY_LENGTH, stdin)==NULL)
{
return (-1);
}
passphrase_match[strlen(passphrase_match) - 1]= 0;
/*
* Check to make sure they match. It's safe to use strcmp here
* since we're confident both strings will be KEY_LENGTH or fewer
* bytes.
*/
if (strcmp(passphrase, passphrase_match))
{
if (retry<= 0)
{
/* we've run through this RETRY_THRESHOLD times, we're done */
fprintf(stderr, "\nyou're hopeless; get typing lessons \n");
errno= EPERH; /* this is as good as any I suppose */
return (-1);
}
fprintf(stderr, "\nno doofus, they don't match, try again\n");
retry--;
goto again;
}
memset(passphrase_match, 0, KEY_LENGTH);
return (1);
}
int
make_key(struct roil_pack *rp, u_char *key)
{
int len;
u_char *p;
strncpy(rp->md, "sha1", 4);
p= roil_digest(rp, &len);
if (p==NULL)
{
/* error set in roil_digest() */
return (-1);
}
memcpy(key, p, len);
return (1);
}
void
get_iv(u_char *iv)
{
int n;
/* XXX - should use the rand() interface from OpenSSL */
srandom((unsigned)time(NULL));
/* get 8 bytes of pseudo random value, from 0 - 255 */
for (n= 0; n < IV_LENGTH; n++)
{
iv[n]= random() % 0xff;
}
}
void
usage(char *name)
{
printf("usage %s [options] file n"
"-e cipher_type\t\tencrypt\n"
"-d\t\t tdecrypt\n"
"-h\t\t\tthis blurb you see right here n"
"-m message_digest\tmessage digest\n", name);
}
/* EOF */