Encrypting and Decrypting PKCS7 and SMIME

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