264
This program is called with the first argument as either sign or verify. If signing mode is used, we will expect the next argument to be the private key, the following argument to be the
corresponding certificate, and the rest can be chain certificates that well add to the message. In verification mode, the third argument is expected to be the certificate, and the rest are extra
certificates to check the signature.
We use the function
create_store
to represent the setup process for the certificate store abstractly. We read in the appropriate number of arguments based on the mode and add all the rest
to a certificate stack. Finally, we either sign and emit the SMIME message or read the SMIME message and emit the original, verified message.
10.4.2 Encrypting and Decrypting
Again, were familiar with the general process; we need a peers public key to encrypt and our own private key to decrypt. The functions for reading and writing the PKCS7 objects in the SMIME
encoding are unchanged, but we do have the new functions
PKCS7_encrypt
and
PKCS7_decrypt
. Before delving into the details of these two new functions, we should go back and think of the envelope interface we saw in
Chapter 8 . This interface allowed us to encrypt
messages for other users with simple function calls and public key components, but in reality, the majority of the encryption was done using a symmetric cipher. These PKCS7 functions do the
same thing. They generate a random key and encrypt the data with it. Then the random key, or session key, is encrypted using the recipients public key and included with the message. As an
extension, PKCS7 allows us to send a single encrypted message to multiple users by simply encrypting the session key with each of the recipients public keys and including all of that data
with the message. The example below will allow us to do this.
PKCS7 PKCS7_encryptSTACK_OFX509 certs, BIO in, const EVP_CIPHER cipher,
int flags;
The first argument is a collection of public keys for the recipients. Each public key will be used to encrypt the messages session key separately. The second argument specifies the
BIO
from which the message to encrypt will be read. The third argument specifies the symmetric algorithm to use, and the last are the flags, discussed below.
int PKCS7_decryptPKCS7 p7, EVP_PKEY pkey, X509 cert, BIO data, int flags;
The decryption function is equally simple. The
PKCS7
object is passed in first; it is the product of a call to
SMIME_read_PKCS7
. The next two arguments are accounted for by the private key to perform the decryption and the corresponding certificate. The
BIO
object is used by the
PKCS7_decrypt
to write out the decrypted data. Again, the flags are discussed below.
We will look at another small utility, just as we did for signing and verifying, to make clear the kind of setup we need to do before calling these functions.
Example 10-9 has that code.
Example 10-9. A utility to encrypt and decrypt SMIME messages
include stdio.h include stdlib.h
include opensslcrypto.h include opensslerr.h
include opensslpem.h include opensslrand.h
265
int mainint argc, char argv[] {
int encrypt; PKCS7 pkcs7;
const EVP_CIPHER cipher; STACK_OFX509 certs;
X509 cert; EVP_PKEY pkey;
FILE fp; BIO pkcs7_bio, in, out;
OpenSSL_add_all_algorithms; ERR_load_crypto_strings;
seed_prng; --argc, ++argv;
if argc 2 {
fprintfstderr, Usage: ed encrypt|decrypt [privkey.pem] cert.pem
...\n; goto err;
} if strcmpargv, encrypt
encrypt = 1; else ifstrcmpargv, decrypt
encrypt = 0; else
{ fprintfstderr, Usage: ed encrypt|decrypt [privkey.pem]
cert.pem ...\n;
goto err; }
--argc, ++argv; setup the BIO objects for stdin and stdout
if in = BIO_new_fpstdin, BIO_NOCLOSE || out = BIO_new_fpstdout, BIO_NOCLOSE
{ fprintfstderr, Error creating BIO objects\n;
goto err; }
if encrypt {
choose cipher and read in all certificates as encryption targets
cipher = EVP_des_ede3_cbc; certs = sk_X509_new_null;
while argc {
X509 tmp; if fp = fopenargv, r ||
tmp = PEM_read_X509fp, NULL, NULL, NULL {
fprintfstderr, Error reading encryption certificate in s\n,
argv;
266
goto err; }
sk_X509_pushcerts, tmp; fclosefp;
--argc, ++argv; }
if pkcs7 = PKCS7_encryptcerts, in, cipher, 0 {
ERR_print_errors_fpstderr; fprintfstderr, Error making the PKCS7 object\n;
goto err; }
if SMIME_write_PKCS7out, pkcs7, in, 0 = 1 {
fprintfstderr, Error writing the SMIME data\n; goto err;
} }
else {
if fp = fopenargv, r || pkey = PEM_read_PrivateKeyfp, NULL, NULL, NULL
{ fprintfstderr, Error reading private key in s\n,
argv; goto err;
} fclosefp;
--argc, ++argv; if fp = fopenargv, r ||
cert = PEM_read_X509fp, NULL, NULL, NULL {
fprintfstderr, Error reading decryption certificate in s\n,
argv; goto err;
} fclosefp;
--argc, ++argv; if argc
fprintfstderr, Warning: excess parameters specified. Ignoring...\n;
if pkcs7 = SMIME_read_PKCS7in, pkcs7_bio {
fprintfstderr, Error reading PKCS7 object\n; goto err;
} if PKCS7_decryptpkcs7, pkey, cert, out, 0 = 1
{ fprintfstderr, Error decrypting PKCS7 object\n;
goto err; }
} return 0;
err: return -1;
}
267
This program is similar to the one in Example 10-8
. When in encryption mode, it expects all the arguments after the word encrypt to be certificate files for the recipients. In decryption mode,
the argument after decrypt must be the private key filename. The following argument should be the corresponding certificate; all further arguments are ignored, and a warning is emitted.
Analyzing this program, we can see several similarities to the previous example. When encrypting, we create the recipient stack, create the
PKCS7
object with it and the message, and then write the product out. Decryption requires us to get the private key and certificate before performing the
PKCS7 and SMIME operations.
10.4.3 Combined Operations