878 lines
22 KiB
C
Executable File
878 lines
22 KiB
C
Executable File
/*
|
|
* Heirloom mailx - a mail user agent derived from Berkeley Mail.
|
|
*
|
|
* Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
|
|
*/
|
|
/*
|
|
* Copyright (c) 2002
|
|
* Gunnar Ritter. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by Gunnar Ritter
|
|
* and his contributors.
|
|
* 4. Neither the name of Gunnar Ritter nor the names of his contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifndef lint
|
|
#ifdef DOSCCS
|
|
static char sccsid[] = "@(#)openssl.c 1.26 (gritter) 5/26/09";
|
|
#endif
|
|
#endif /* not lint */
|
|
|
|
#include "config.h"
|
|
#include <setjmp.h>
|
|
#include <termios.h>
|
|
#include <stdio.h>
|
|
|
|
static int verbose;
|
|
static int reset_tio;
|
|
static struct termios otio;
|
|
static sigjmp_buf ssljmp;
|
|
|
|
#include <openssl/crypto.h>
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/x509v3.h>
|
|
#include <openssl/x509.h>
|
|
#include <openssl/pem.h>
|
|
#include <openssl/rand.h>
|
|
|
|
#include "rcv.h"
|
|
#include <errno.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
|
|
#include <sys/socket.h>
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#ifdef HAVE_ARPA_INET_H
|
|
#include <arpa/inet.h>
|
|
#endif /* HAVE_ARPA_INET_H */
|
|
|
|
#include <dirent.h>
|
|
|
|
#include "extern.h"
|
|
|
|
/*
|
|
* Mail -- a mail program
|
|
*
|
|
* SSL functions
|
|
*/
|
|
|
|
/*
|
|
* OpenSSL client implementation according to: John Viega, Matt Messier,
|
|
* Pravir Chandra: Network Security with OpenSSL. Sebastopol, CA 2002.
|
|
*/
|
|
|
|
static int initialized;
|
|
static int rand_init;
|
|
static int message_number;
|
|
static int verify_error_found;
|
|
|
|
static void sslcatch(int s);
|
|
static int ssl_rand_init(void);
|
|
static void ssl_init(void);
|
|
static int ssl_verify_cb(int success, X509_STORE_CTX *store);
|
|
static const SSL_METHOD *ssl_select_method(const char *uhp);
|
|
static void ssl_load_verifications(struct sock *sp);
|
|
static void ssl_certificate(struct sock *sp, const char *uhp);
|
|
static enum okay ssl_check_host(const char *server, struct sock *sp);
|
|
static EVP_CIPHER *smime_cipher(const char *name);
|
|
static int ssl_password_cb(char *buf, int size, int rwflag, void *userdata);
|
|
static FILE *smime_sign_cert(const char *xname, const char *xname2, int warn);
|
|
#if defined (X509_V_FLAG_CRL_CHECK) && defined (X509_V_FLAG_CRL_CHECK_ALL)
|
|
static enum okay load_crl1(X509_STORE *store, const char *name);
|
|
#endif
|
|
static enum okay load_crls(X509_STORE *store, const char *vfile,
|
|
const char *vdir);
|
|
|
|
static void
|
|
sslcatch(int s)
|
|
{
|
|
if (reset_tio)
|
|
tcsetattr(0, TCSADRAIN, &otio);
|
|
siglongjmp(ssljmp, s);
|
|
}
|
|
|
|
static int
|
|
ssl_rand_init(void)
|
|
{
|
|
char *cp;
|
|
int state = 0;
|
|
|
|
if ((cp = value("ssl-rand-egd")) != NULL) {
|
|
cp = expand(cp);
|
|
if (RAND_egd(cp) == -1) {
|
|
DEBUG_ERROR( catgets(catd, CATSET, 245,
|
|
"entropy daemon at \"%s\" not available\n"),
|
|
cp);
|
|
} else
|
|
state = 1;
|
|
} else if ((cp = value("ssl-rand-file")) != NULL) {
|
|
cp = expand(cp);
|
|
if (RAND_load_file(cp, 1024) == -1) {
|
|
DEBUG_ERROR( catgets(catd, CATSET, 246,
|
|
"entropy file at \"%s\" not available\n"), cp);
|
|
} else {
|
|
struct stat st;
|
|
|
|
if (stat(cp, &st) == 0 && S_ISREG(st.st_mode) &&
|
|
access(cp, W_OK) == 0) {
|
|
if (RAND_write_file(cp) == -1) {
|
|
DEBUG_ERROR( catgets(catd, CATSET,
|
|
247,
|
|
"writing entropy data to \"%s\" failed\n"), cp);
|
|
}
|
|
}
|
|
state = 1;
|
|
}
|
|
}
|
|
return state;
|
|
}
|
|
|
|
static void
|
|
ssl_init(void)
|
|
{
|
|
verbose = value("verbose") != NULL;
|
|
if (initialized == 0) {
|
|
SSL_library_init();
|
|
initialized = 1;
|
|
}
|
|
if (rand_init == 0)
|
|
rand_init = ssl_rand_init();
|
|
}
|
|
|
|
static int
|
|
ssl_verify_cb(int success, X509_STORE_CTX *store)
|
|
{
|
|
if (success == 0) {
|
|
char data[256];
|
|
X509 *cert = X509_STORE_CTX_get_current_cert(store);
|
|
int depth = X509_STORE_CTX_get_error_depth(store);
|
|
int err = X509_STORE_CTX_get_error(store);
|
|
|
|
verify_error_found = 1;
|
|
if (message_number)
|
|
DEBUG_ERROR("Message %d: ", message_number);
|
|
DEBUG_ERROR( catgets(catd, CATSET, 229,
|
|
"Error with certificate at depth: %i\n"),
|
|
depth);
|
|
X509_NAME_oneline(X509_get_issuer_name(cert), data,
|
|
sizeof data);
|
|
DEBUG_ERROR( catgets(catd, CATSET, 230, " issuer = %s\n"),
|
|
data);
|
|
X509_NAME_oneline(X509_get_subject_name(cert), data,
|
|
sizeof data);
|
|
DEBUG_ERROR( catgets(catd, CATSET, 231, " subject = %s\n"),
|
|
data);
|
|
DEBUG_ERROR( catgets(catd, CATSET, 232, " err %i: %s\n"),
|
|
err, X509_verify_cert_error_string(err));
|
|
if (ssl_vrfy_decide() != OKAY)
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static const SSL_METHOD *
|
|
ssl_select_method(const char *uhp)
|
|
{
|
|
const SSL_METHOD *method;
|
|
char *cp;
|
|
|
|
cp = ssl_method_string(uhp);
|
|
if (cp != NULL) {
|
|
/* lichao added: openssl in trunk does not support ssl2 */
|
|
/*if (equal(cp, "ssl2"))
|
|
method = SSLv2_client_method();
|
|
else */if (equal(cp, "ssl3"))
|
|
method = SSLv3_client_method();
|
|
else if (equal(cp, "tls1"))
|
|
method = TLSv1_client_method();
|
|
else {
|
|
DEBUG_ERROR( catgets(catd, CATSET, 244,
|
|
"Invalid SSL method \"%s\"\n"), cp);
|
|
method = SSLv23_client_method();
|
|
}
|
|
} else
|
|
method = SSLv23_client_method();
|
|
return method;
|
|
}
|
|
|
|
static void
|
|
ssl_load_verifications(struct sock *sp)
|
|
{
|
|
char *ca_dir, *ca_file;
|
|
X509_STORE *store;
|
|
if (ssl_vrfy_level == VRFY_IGNORE)
|
|
return;
|
|
if ((ca_dir = value("ssl-ca-dir")) != NULL)
|
|
ca_dir = expand(ca_dir);
|
|
if ((ca_file = value("ssl-ca-file")) != NULL)
|
|
ca_file = expand(ca_file);
|
|
if (ca_dir || ca_file) {
|
|
if (SSL_CTX_load_verify_locations(sp->s_ctx,
|
|
ca_file, ca_dir) != 1) {
|
|
DEBUG_ERROR( catgets(catd, CATSET, 233,
|
|
"Error loading"));
|
|
if (ca_dir) {
|
|
DEBUG_ERROR( catgets(catd, CATSET, 234,
|
|
" %s"), ca_dir);
|
|
if (ca_file)
|
|
DEBUG_ERROR( catgets(catd, CATSET,
|
|
235, " or"));
|
|
}
|
|
if (ca_file)
|
|
DEBUG_ERROR( catgets(catd, CATSET, 236,
|
|
" %s"), ca_file);
|
|
DEBUG_ERROR( catgets(catd, CATSET, 237, "\n"));
|
|
}
|
|
}
|
|
if (value("ssl-no-default-ca") == NULL) {
|
|
if (SSL_CTX_set_default_verify_paths(sp->s_ctx) != 1)
|
|
DEBUG_ERROR( catgets(catd, CATSET, 243,
|
|
"Error loading default CA locations\n"));
|
|
}
|
|
verify_error_found = 0;
|
|
message_number = 0;
|
|
SSL_CTX_set_verify(sp->s_ctx, SSL_VERIFY_PEER, ssl_verify_cb);
|
|
store = SSL_CTX_get_cert_store(sp->s_ctx);
|
|
load_crls(store, "ssl-crl-file", "ssl-crl-dir");
|
|
}
|
|
|
|
static void
|
|
ssl_certificate(struct sock *sp, const char *uhp)
|
|
{
|
|
char *certvar, *keyvar, *cert, *key;
|
|
|
|
certvar = ac_alloc(strlen(uhp) + 10);
|
|
strcpy(certvar, "ssl-cert-");
|
|
strcpy(&certvar[9], uhp);
|
|
if ((cert = value(certvar)) != NULL ||
|
|
(cert = value("ssl-cert")) != NULL) {
|
|
cert = expand(cert);
|
|
if (SSL_CTX_use_certificate_chain_file(sp->s_ctx, cert) == 1) {
|
|
keyvar = ac_alloc(strlen(uhp) + 9);
|
|
strcpy(keyvar, "ssl-key-");
|
|
if ((key = value(keyvar)) == NULL &&
|
|
(key = value("ssl-key")) == NULL)
|
|
key = cert;
|
|
else
|
|
key = expand(key);
|
|
if (SSL_CTX_use_PrivateKey_file(sp->s_ctx, key,
|
|
SSL_FILETYPE_PEM) != 1)
|
|
DEBUG_ERROR( catgets(catd, CATSET, 238,
|
|
"cannot load private key from file %s\n"),
|
|
key);
|
|
ac_free(keyvar);
|
|
} else
|
|
DEBUG_ERROR( catgets(catd, CATSET, 239,
|
|
"cannot load certificate from file %s\n"),
|
|
cert);
|
|
}
|
|
ac_free(certvar);
|
|
}
|
|
|
|
static enum okay
|
|
ssl_check_host(const char *server, struct sock *sp)
|
|
{
|
|
X509 *cert;
|
|
X509_NAME *subj;
|
|
char data[256];
|
|
#ifdef HAVE_STACK_OF
|
|
STACK_OF(GENERAL_NAME) *gens;
|
|
#else
|
|
/*GENERAL_NAMES*/STACK *gens;
|
|
#endif
|
|
GENERAL_NAME *gen;
|
|
int i;
|
|
|
|
if ((cert = SSL_get_peer_certificate(sp->s_ssl)) == NULL) {
|
|
DEBUG_ERROR( catgets(catd, CATSET, 248,
|
|
"no certificate from \"%s\"\n"), server);
|
|
return STOP;
|
|
}
|
|
gens = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
|
|
if (gens != NULL) {
|
|
for (i = 0; i < sk_GENERAL_NAME_num(gens); i++) {
|
|
gen = sk_GENERAL_NAME_value(gens, i);
|
|
if (gen->type == GEN_DNS) {
|
|
if (verbose)
|
|
DEBUG_ERROR(
|
|
"Comparing DNS name: \"%s\"\n",
|
|
gen->d.ia5->data);
|
|
if (rfc2595_hostname_match(server,
|
|
(char *)gen->d.ia5->data)
|
|
== OKAY)
|
|
goto found;
|
|
}
|
|
}
|
|
}
|
|
if ((subj = X509_get_subject_name(cert)) != NULL &&
|
|
X509_NAME_get_text_by_NID(subj, NID_commonName,
|
|
data, sizeof data) > 0) {
|
|
data[sizeof data - 1] = 0;
|
|
if (verbose)
|
|
DEBUG_ERROR("Comparing common name: \"%s\"\n",
|
|
data);
|
|
if (rfc2595_hostname_match(server, data) == OKAY)
|
|
goto found;
|
|
}
|
|
X509_free(cert);
|
|
return STOP;
|
|
found: X509_free(cert);
|
|
return OKAY;
|
|
}
|
|
|
|
enum okay
|
|
ssl_open(const char *server, struct sock *sp, const char *uhp)
|
|
{
|
|
char *cp;
|
|
long options;
|
|
|
|
ssl_init();
|
|
ssl_set_vrfy_level(uhp);
|
|
if ((sp->s_ctx =
|
|
SSL_CTX_new((SSL_METHOD *)ssl_select_method(uhp))) == NULL) {
|
|
ssl_gen_err(catgets(catd, CATSET, 261, "SSL_CTX_new() failed"));
|
|
return STOP;
|
|
}
|
|
#ifdef SSL_MODE_AUTO_RETRY
|
|
/* available with OpenSSL 0.9.6 or later */
|
|
SSL_CTX_set_mode(sp->s_ctx, SSL_MODE_AUTO_RETRY);
|
|
#endif /* SSL_MODE_AUTO_RETRY */
|
|
options = SSL_OP_ALL;
|
|
if (value("ssl-v2-allow") == NULL)
|
|
options |= SSL_OP_NO_SSLv2;
|
|
SSL_CTX_set_options(sp->s_ctx, options);
|
|
ssl_load_verifications(sp);
|
|
ssl_certificate(sp, uhp);
|
|
if ((cp = value("ssl-cipher-list")) != NULL) {
|
|
if (SSL_CTX_set_cipher_list(sp->s_ctx, cp) != 1)
|
|
DEBUG_ERROR( catgets(catd, CATSET, 240,
|
|
"invalid ciphers: %s\n"), cp);
|
|
}
|
|
if ((sp->s_ssl = SSL_new(sp->s_ctx)) == NULL) {
|
|
ssl_gen_err(catgets(catd, CATSET, 262, "SSL_new() failed"));
|
|
return STOP;
|
|
}
|
|
SSL_set_fd(sp->s_ssl, sp->s_fd);
|
|
if (SSL_connect(sp->s_ssl) < 0) {
|
|
ssl_gen_err(catgets(catd, CATSET, 263,
|
|
"could not initiate SSL/TLS connection"));
|
|
return STOP;
|
|
}
|
|
if (ssl_vrfy_level != VRFY_IGNORE) {
|
|
if (ssl_check_host(server, sp) != OKAY) {
|
|
DEBUG_ERROR( catgets(catd, CATSET, 249,
|
|
"host certificate does not match \"%s\"\n"),
|
|
server);
|
|
if (ssl_vrfy_decide() != OKAY)
|
|
return STOP;
|
|
}
|
|
}
|
|
sp->s_use_ssl = 1;
|
|
return OKAY;
|
|
}
|
|
|
|
void
|
|
ssl_gen_err(const char *fmt, ...)
|
|
{
|
|
#if (DEBUG_LEVEL >= DEBUG_LEVEL_ERROR)
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
SSL_load_error_strings();
|
|
DEBUG_ERROR(": %s\n",
|
|
(ERR_error_string(ERR_get_error(), NULL)));
|
|
#endif
|
|
}
|
|
|
|
FILE *
|
|
smime_sign(FILE *ip, struct header *headp)
|
|
{
|
|
FILE *sp, *fp, *bp, *hp;
|
|
char *cp, *addr;
|
|
X509 *cert;
|
|
PKCS7 *pkcs7;
|
|
EVP_PKEY *pkey;
|
|
BIO *bb, *sb;
|
|
|
|
ssl_init();
|
|
if ((addr = myorigin(headp)) == NULL) {
|
|
DEBUG_ERROR("No \"from\" address for signing specified\n");
|
|
return NULL;
|
|
}
|
|
if ((fp = smime_sign_cert(addr, NULL, 1)) == NULL)
|
|
return NULL;
|
|
if ((pkey = PEM_read_PrivateKey(fp, NULL, ssl_password_cb, NULL))
|
|
== NULL) {
|
|
ssl_gen_err("Error reading private key from");
|
|
Fclose(fp);
|
|
return NULL;
|
|
}
|
|
rewind(fp);
|
|
if ((cert = PEM_read_X509(fp, NULL, ssl_password_cb, NULL)) == NULL) {
|
|
ssl_gen_err("Error reading signer certificate from");
|
|
Fclose(fp);
|
|
EVP_PKEY_free(pkey);
|
|
return NULL;
|
|
}
|
|
Fclose(fp);
|
|
if ((sp = Ftemp(&cp, "Rs", "w+", 0600, 1)) == NULL) {
|
|
perror("tempfile");
|
|
X509_free(cert);
|
|
EVP_PKEY_free(pkey);
|
|
return NULL;
|
|
}
|
|
unlink(cp);
|
|
Ftfree(&cp);
|
|
rewind(ip);
|
|
if (smime_split(ip, &hp, &bp, -1, 0) == STOP) {
|
|
Fclose(sp);
|
|
X509_free(cert);
|
|
EVP_PKEY_free(pkey);
|
|
return NULL;
|
|
}
|
|
if ((bb = BIO_new_fp(bp, BIO_NOCLOSE)) == NULL ||
|
|
(sb = BIO_new_fp(sp, BIO_NOCLOSE)) == NULL) {
|
|
ssl_gen_err("Error creating BIO signing objects");
|
|
Fclose(sp);
|
|
X509_free(cert);
|
|
EVP_PKEY_free(pkey);
|
|
return NULL;
|
|
}
|
|
if ((pkcs7 = PKCS7_sign(cert, pkey, NULL, bb,
|
|
PKCS7_DETACHED)) == NULL) {
|
|
ssl_gen_err("Error creating the PKCS#7 signing object");
|
|
BIO_free(bb);
|
|
BIO_free(sb);
|
|
Fclose(sp);
|
|
X509_free(cert);
|
|
EVP_PKEY_free(pkey);
|
|
return NULL;
|
|
}
|
|
if (PEM_write_bio_PKCS7(sb, pkcs7) == 0) {
|
|
ssl_gen_err("Error writing signed S/MIME data");
|
|
BIO_free(bb);
|
|
BIO_free(sb);
|
|
Fclose(sp);
|
|
X509_free(cert);
|
|
EVP_PKEY_free(pkey);
|
|
return NULL;
|
|
}
|
|
BIO_free(bb);
|
|
BIO_free(sb);
|
|
X509_free(cert);
|
|
EVP_PKEY_free(pkey);
|
|
rewind(bp);
|
|
fflush(sp);
|
|
rewind(sp);
|
|
return smime_sign_assemble(hp, bp, sp);
|
|
}
|
|
|
|
static EVP_CIPHER *
|
|
smime_cipher(const char *name)
|
|
{
|
|
const EVP_CIPHER *cipher;
|
|
char *vn, *cp;
|
|
int vs;
|
|
|
|
vn = ac_alloc(vs = strlen(name) + 30);
|
|
snprintf(vn, vs, "smime-cipher-%s", name);
|
|
if ((cp = value(vn)) != NULL) {
|
|
if (strcmp(cp, "rc2-40") == 0)
|
|
cipher = EVP_rc2_40_cbc();
|
|
else if (strcmp(cp, "rc2-64") == 0)
|
|
cipher = EVP_rc2_64_cbc();
|
|
else if (strcmp(cp, "des") == 0)
|
|
cipher = EVP_des_cbc();
|
|
else if (strcmp(cp, "des-ede3") == 0)
|
|
cipher = EVP_des_ede3_cbc();
|
|
else {
|
|
DEBUG_ERROR("Invalid cipher \"%s\".\n", cp);
|
|
cipher = NULL;
|
|
}
|
|
} else
|
|
cipher = EVP_des_ede3_cbc();
|
|
ac_free(vn);
|
|
return (EVP_CIPHER *)cipher;
|
|
}
|
|
|
|
FILE *
|
|
smime_encrypt(FILE *ip, const char *certfile, const char *to)
|
|
{
|
|
FILE *yp, *fp, *bp, *hp;
|
|
char *cp;
|
|
X509 *cert;
|
|
PKCS7 *pkcs7;
|
|
BIO *bb, *yb;
|
|
#ifdef HAVE_STACK_OF
|
|
STACK_OF(X509) *certs;
|
|
#else
|
|
STACK *certs;
|
|
#endif
|
|
EVP_CIPHER *cipher;
|
|
|
|
certfile = expand((char *)certfile);
|
|
ssl_init();
|
|
if ((cipher = smime_cipher(to)) == NULL)
|
|
return NULL;
|
|
if ((fp = Fopen(certfile, "r")) == NULL) {
|
|
perror(certfile);
|
|
return NULL;
|
|
}
|
|
if ((cert = PEM_read_X509(fp, NULL, ssl_password_cb, NULL)) == NULL) {
|
|
ssl_gen_err("Error reading encryption certificate from \"%s\"",
|
|
certfile);
|
|
Fclose(fp);
|
|
return NULL;
|
|
}
|
|
Fclose(fp);
|
|
certs = sk_X509_new_null();
|
|
sk_X509_push(certs, cert);
|
|
if ((yp = Ftemp(&cp, "Ry", "w+", 0600, 1)) == NULL) {
|
|
perror("tempfile");
|
|
return NULL;
|
|
}
|
|
unlink(cp);
|
|
Ftfree(&cp);
|
|
rewind(ip);
|
|
if (smime_split(ip, &hp, &bp, -1, 0) == STOP) {
|
|
Fclose(yp);
|
|
return NULL;
|
|
}
|
|
if ((bb = BIO_new_fp(bp, BIO_NOCLOSE)) == NULL ||
|
|
(yb = BIO_new_fp(yp, BIO_NOCLOSE)) == NULL) {
|
|
ssl_gen_err("Error creating BIO encryption objects");
|
|
Fclose(yp);
|
|
return NULL;
|
|
}
|
|
if ((pkcs7 = PKCS7_encrypt(certs, bb, cipher, 0)) == NULL) {
|
|
ssl_gen_err("Error creating the PKCS#7 encryption object");
|
|
BIO_free(bb);
|
|
BIO_free(yb);
|
|
Fclose(yp);
|
|
return NULL;
|
|
}
|
|
if (PEM_write_bio_PKCS7(yb, pkcs7) == 0) {
|
|
ssl_gen_err("Error writing encrypted S/MIME data");
|
|
BIO_free(bb);
|
|
BIO_free(yb);
|
|
Fclose(yp);
|
|
return NULL;
|
|
}
|
|
BIO_free(bb);
|
|
BIO_free(yb);
|
|
Fclose(bp);
|
|
fflush(yp);
|
|
rewind(yp);
|
|
return smime_encrypt_assemble(hp, yp);
|
|
}
|
|
|
|
struct message *
|
|
smime_decrypt(struct message *m, const char *to, const char *cc, int signcall)
|
|
{
|
|
FILE *fp, *bp, *hp, *op;
|
|
char *cp;
|
|
X509 *cert = NULL;
|
|
PKCS7 *pkcs7;
|
|
EVP_PKEY *pkey = NULL;
|
|
BIO *bb, *pb, *ob;
|
|
long size = m->m_size;
|
|
FILE *yp;
|
|
|
|
if ((yp = setinput(&mb, m, NEED_BODY)) == NULL)
|
|
return NULL;
|
|
ssl_init();
|
|
if ((fp = smime_sign_cert(to, cc, 0)) != NULL) {
|
|
if ((pkey = PEM_read_PrivateKey(fp, NULL, ssl_password_cb,
|
|
NULL)) == NULL) {
|
|
ssl_gen_err("Error reading private key");
|
|
Fclose(fp);
|
|
return NULL;
|
|
}
|
|
rewind(fp);
|
|
if ((cert = PEM_read_X509(fp, NULL, ssl_password_cb,
|
|
NULL)) == NULL) {
|
|
ssl_gen_err("Error reading decryption certificate");
|
|
Fclose(fp);
|
|
EVP_PKEY_free(pkey);
|
|
return NULL;
|
|
}
|
|
Fclose(fp);
|
|
}
|
|
if ((op = Ftemp(&cp, "Rp", "w+", 0600, 1)) == NULL) {
|
|
perror("tempfile");
|
|
if (cert)
|
|
X509_free(cert);
|
|
if (pkey)
|
|
EVP_PKEY_free(pkey);
|
|
return NULL;
|
|
}
|
|
unlink(cp);
|
|
Ftfree(&cp);
|
|
if (smime_split(yp, &hp, &bp, size, 1) == STOP) {
|
|
Fclose(op);
|
|
if (cert)
|
|
X509_free(cert);
|
|
if (pkey)
|
|
EVP_PKEY_free(pkey);
|
|
return NULL;
|
|
}
|
|
if ((ob = BIO_new_fp(op, BIO_NOCLOSE)) == NULL ||
|
|
(bb = BIO_new_fp(bp, BIO_NOCLOSE)) == NULL) {
|
|
ssl_gen_err("Error creating BIO decryption objects");
|
|
Fclose(op);
|
|
if (cert)
|
|
X509_free(cert);
|
|
if (pkey)
|
|
EVP_PKEY_free(pkey);
|
|
return NULL;
|
|
}
|
|
if ((pkcs7 = SMIME_read_PKCS7(bb, &pb)) == NULL) {
|
|
ssl_gen_err("Error reading PKCS#7 object");
|
|
Fclose(op);
|
|
if (cert)
|
|
X509_free(cert);
|
|
if (pkey)
|
|
EVP_PKEY_free(pkey);
|
|
return NULL;
|
|
}
|
|
if (PKCS7_type_is_signed(pkcs7)) {
|
|
if (signcall) {
|
|
BIO_free(bb);
|
|
BIO_free(ob);
|
|
if (cert)
|
|
X509_free(cert);
|
|
if (pkey)
|
|
EVP_PKEY_free(pkey);
|
|
Fclose(op);
|
|
Fclose(bp);
|
|
Fclose(hp);
|
|
setinput(&mb, m, NEED_BODY);
|
|
return (struct message *)-1;
|
|
}
|
|
if (PKCS7_verify(pkcs7, NULL, NULL, NULL, ob,
|
|
PKCS7_NOVERIFY|PKCS7_NOSIGS) != 1)
|
|
goto err;
|
|
fseek(hp, 0L, SEEK_END);
|
|
fprintf(hp, "X-Encryption-Cipher: none\n");
|
|
fflush(hp);
|
|
rewind(hp);
|
|
} else if (pkey == NULL) {
|
|
DEBUG_ERROR("No appropriate private key found.\n");
|
|
goto err2;
|
|
} else if (cert == NULL) {
|
|
DEBUG_ERROR("No appropriate certificate found.\n");
|
|
goto err2;
|
|
} else if (PKCS7_decrypt(pkcs7, pkey, cert, ob, 0) != 1) {
|
|
err: ssl_gen_err("Error decrypting PKCS#7 object");
|
|
err2: BIO_free(bb);
|
|
BIO_free(ob);
|
|
Fclose(op);
|
|
Fclose(bp);
|
|
Fclose(hp);
|
|
if (cert)
|
|
X509_free(cert);
|
|
if (pkey)
|
|
EVP_PKEY_free(pkey);
|
|
return NULL;
|
|
}
|
|
BIO_free(bb);
|
|
BIO_free(ob);
|
|
if (cert)
|
|
X509_free(cert);
|
|
if (pkey)
|
|
EVP_PKEY_free(pkey);
|
|
fflush(op);
|
|
rewind(op);
|
|
Fclose(bp);
|
|
return smime_decrypt_assemble(m, hp, op);
|
|
}
|
|
|
|
/*ARGSUSED4*/
|
|
static int
|
|
ssl_password_cb(char *buf, int size, int rwflag, void *userdata)
|
|
{
|
|
sighandler_type saveint;
|
|
char *pass = NULL;
|
|
int len;
|
|
|
|
(void)&saveint;
|
|
(void)&pass;
|
|
saveint = safe_signal(SIGINT, SIG_IGN);
|
|
if (sigsetjmp(ssljmp, 1) == 0) {
|
|
if (saveint != SIG_IGN)
|
|
safe_signal(SIGINT, sslcatch);
|
|
pass = getpassword(&otio, &reset_tio, "PEM pass phrase:");
|
|
}
|
|
safe_signal(SIGINT, saveint);
|
|
if (pass == NULL)
|
|
return 0;
|
|
len = strlen(pass);
|
|
if (len > size)
|
|
len = size;
|
|
memcpy(buf, pass, len);
|
|
return len;
|
|
}
|
|
|
|
static FILE *
|
|
smime_sign_cert(const char *xname, const char *xname2, int warn)
|
|
{
|
|
char *vn, *cp;
|
|
int vs;
|
|
FILE *fp;
|
|
struct name *np;
|
|
const char *name = xname, *name2 = xname2;
|
|
|
|
loop: if (name) {
|
|
np = sextract(savestr(name), GTO|GSKIN);
|
|
while (np) {
|
|
/*
|
|
* This needs to be more intelligent since it will
|
|
* currently take the first name for which a private
|
|
* key is available regardless of whether it is the
|
|
* right one for the message.
|
|
*/
|
|
vn = ac_alloc(vs = strlen(np->n_name) + 30);
|
|
snprintf(vn, vs, "smime-sign-cert-%s", np->n_name);
|
|
if ((cp = value(vn)) != NULL)
|
|
goto open;
|
|
np = np->n_flink;
|
|
}
|
|
if (name2) {
|
|
name = name2;
|
|
name2 = NULL;
|
|
goto loop;
|
|
}
|
|
}
|
|
if ((cp = value("smime-sign-cert")) != NULL)
|
|
goto open;
|
|
if (warn) {
|
|
DEBUG_ERROR("Could not find a certificate for %s", xname);
|
|
if (xname2)
|
|
DEBUG_ERROR("or %s", xname2);
|
|
DEBUG_ERROR("%c", '\n');
|
|
}
|
|
return NULL;
|
|
open: cp = expand(cp);
|
|
if ((fp = Fopen(cp, "r")) == NULL) {
|
|
perror(cp);
|
|
return NULL;
|
|
}
|
|
return fp;
|
|
}
|
|
|
|
#if defined (X509_V_FLAG_CRL_CHECK) && defined (X509_V_FLAG_CRL_CHECK_ALL)
|
|
static enum okay
|
|
load_crl1(X509_STORE *store, const char *name)
|
|
{
|
|
X509_LOOKUP *lookup;
|
|
|
|
if (verbose)
|
|
DEBUG_PRINT("Loading CRL from \"%s\".\n", name);
|
|
if ((lookup = X509_STORE_add_lookup(store,
|
|
X509_LOOKUP_file())) == NULL) {
|
|
ssl_gen_err("Error creating X509 lookup object");
|
|
return STOP;
|
|
}
|
|
if (X509_load_crl_file(lookup, name, X509_FILETYPE_PEM) != 1) {
|
|
ssl_gen_err("Error loading CRL from \"%s\"", name);
|
|
return STOP;
|
|
}
|
|
return OKAY;
|
|
}
|
|
#endif /* new OpenSSL */
|
|
|
|
static enum okay
|
|
load_crls(X509_STORE *store, const char *vfile, const char *vdir)
|
|
{
|
|
char *crl_file, *crl_dir;
|
|
#if defined (X509_V_FLAG_CRL_CHECK) && defined (X509_V_FLAG_CRL_CHECK_ALL)
|
|
DIR *dirfd;
|
|
struct dirent *dp;
|
|
char *fn = NULL;
|
|
int fs = 0, ds, es;
|
|
#endif /* new OpenSSL */
|
|
|
|
if ((crl_file = value(vfile)) != NULL) {
|
|
#if defined (X509_V_FLAG_CRL_CHECK) && defined (X509_V_FLAG_CRL_CHECK_ALL)
|
|
crl_file = expand(crl_file);
|
|
if (load_crl1(store, crl_file) != OKAY)
|
|
return STOP;
|
|
#else /* old OpenSSL */
|
|
DEBUG_ERROR(
|
|
"This OpenSSL version is too old to use CRLs.\n");
|
|
return STOP;
|
|
#endif /* old OpenSSL */
|
|
}
|
|
if ((crl_dir = value(vdir)) != NULL) {
|
|
#if defined (X509_V_FLAG_CRL_CHECK) && defined (X509_V_FLAG_CRL_CHECK_ALL)
|
|
crl_dir = expand(crl_dir);
|
|
ds = strlen(crl_dir);
|
|
if ((dirfd = opendir(crl_dir)) == NULL) {
|
|
perror(crl_dir);
|
|
return STOP;
|
|
}
|
|
fn = smalloc(fs = ds + 20);
|
|
strcpy(fn, crl_dir);
|
|
fn[ds] = '/';
|
|
while ((dp = readdir(dirfd)) != NULL) {
|
|
if (dp->d_name[0] == '.' &&
|
|
(dp->d_name[1] == '\0' ||
|
|
(dp->d_name[1] == '.' &&
|
|
dp->d_name[2] == '\0')))
|
|
continue;
|
|
if (dp->d_name[0] == '.')
|
|
continue;
|
|
if (ds + (es = strlen(dp->d_name)) + 2 < fs)
|
|
fn = srealloc(fn, fs = ds + es + 20);
|
|
strcpy(&fn[ds+1], dp->d_name);
|
|
if (load_crl1(store, fn) != OKAY) {
|
|
closedir(dirfd);
|
|
free(fn);
|
|
return STOP;
|
|
}
|
|
}
|
|
closedir(dirfd);
|
|
free(fn);
|
|
#else /* old OpenSSL */
|
|
DEBUG_ERROR(
|
|
"This OpenSSL version is too old to use CRLs.\n");
|
|
return STOP;
|
|
#endif /* old OpenSSL */
|
|
}
|
|
#if defined (X509_V_FLAG_CRL_CHECK) && defined (X509_V_FLAG_CRL_CHECK_ALL)
|
|
if (crl_file || crl_dir)
|
|
X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK |
|
|
X509_V_FLAG_CRL_CHECK_ALL);
|
|
#endif /* old OpenSSL */
|
|
return OKAY;
|
|
}
|