imapfilter/cert.c

236 lines
4.3 KiB
C

#ifndef NO_SSLTLS
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <sys/stat.h>
#include <unistd.h>
#include "imapfilter.h"
#include "session.h"
#include "pathnames.h"
#include <openssl/x509.h>
#include <openssl/ssl.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
extern environment env;
int check_cert(X509 *pcert, unsigned char *pmd, unsigned int *pmdlen);
void print_cert(X509 *cert, unsigned char *md, unsigned int *mdlen);
int write_cert(X509 *cert);
int mismatch_cert(void);
/*
* Get SSL/TLS certificate check it, maybe ask user about it and act
* accordingly.
*/
int
get_cert(session *ssn)
{
X509 *cert;
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int mdlen;
mdlen = 0;
if (!(cert = SSL_get_peer_certificate(ssn->ssl)))
return -1;
if (!(X509_digest(cert, EVP_md5(), md, &mdlen)))
return -1;
switch (check_cert(cert, md, &mdlen)) {
case 0:
if (isatty(STDIN_FILENO) == 0)
fatal(ERROR_CERTIFICATE, "%s\n",
"can't accept certificate in non-interactive mode");
print_cert(cert, md, &mdlen);
if (write_cert(cert) == -1)
goto fail;
break;
case -1:
if (isatty(STDIN_FILENO) == 0)
fatal(ERROR_CERTIFICATE, "%s\n",
"certificate mismatch in non-interactive mode");
print_cert(cert, md, &mdlen);
if (mismatch_cert() == -1)
goto fail;
break;
}
X509_free(cert);
return 0;
fail:
X509_free(cert);
return -1;
}
/*
* Check if the SSL/TLS certificate exists in the certificates file.
*/
int
check_cert(X509 *pcert, unsigned char *pmd, unsigned int *pmdlen)
{
int n, r;
FILE *fd;
char b;
char *certf;
X509 *cert;
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int mdlen;
r = 0;
cert = NULL;
n = snprintf(&b, 1, "%s/%s", env.home, PATHNAME_CERTS);
if (env.pathmax != -1 && n > env.pathmax)
fatal(ERROR_PATHNAME,
"pathname limit %ld exceeded: %d\n", env.pathmax, n);
certf = (char *)xmalloc((n + 1) * sizeof(char));
snprintf(certf, n + 1, "%s/%s", env.home, PATHNAME_CERTS);
if (!exists_file(certf)) {
xfree(certf);
return 0;
}
fd = fopen(certf, "r");
xfree(certf);
if (fd == NULL)
return -1;
while ((cert = PEM_read_X509(fd, &cert, NULL, NULL)) != NULL) {
if (X509_subject_name_cmp(cert, pcert) != 0 ||
X509_issuer_name_cmp(cert, pcert) != 0)
continue;
if (!X509_digest(cert, EVP_md5(), md, &mdlen) ||
*pmdlen != mdlen)
continue;
if (memcmp(pmd, md, mdlen) != 0) {
r = -1;
break;
}
r = 1;
break;
}
fclose(fd);
X509_free(cert);
return r;
}
/*
* Print information about the SSL/TLS certificate.
*/
void
print_cert(X509 *cert, unsigned char *md, unsigned int *mdlen)
{
unsigned int i;
char *c;
c = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
printf("Server certificate subject: %s\n", c);
xfree(c);
c = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);
printf("Server certificate issuer: %s\n", c);
xfree(c);
printf("Server key fingerprint: ");
for (i = 0; i < *mdlen; i++)
printf(i != *mdlen - 1 ? "%02X:" : "%02X\n", md[i]);
}
/*
* Write the SSL/TLS certificate after asking the user to accept/reject it.
*/
int
write_cert(X509 *cert)
{
int n;
FILE *fd;
char b, c, buf[64];
char *certf;
do {
printf("(R)eject, accept (t)emporarily or "
"accept (p)ermanently? ");
if (fgets(buf, sizeof(buf), stdin) == NULL)
return -1;
c = tolower((int)(*buf));
} while (c != 'r' && c != 't' && c != 'p');
if (c == 'r')
return -1;
else if (c == 't')
return 0;
n = snprintf(&b, 1, "%s/%s", env.home, PATHNAME_CERTS);
if (env.pathmax != -1 && n > env.pathmax)
fatal(ERROR_PATHNAME,
"pathname limit %ld exceeded: %d\n", env.pathmax, n);
certf = (char *)xmalloc((n + 1) * sizeof(char));
snprintf(certf, n + 1, "%s/%s", env.home, PATHNAME_CERTS);
create_file(certf, S_IRUSR | S_IWUSR);
fd = fopen(certf, "a");
xfree(certf);
if (fd == NULL)
return -1;
PEM_write_X509(fd, cert);
fclose(fd);
return 0;
}
/*
* Ask user to proceed, while a fingerprint mismatch in the SSL/TLS certificate
* was found.
*/
int
mismatch_cert(void)
{
char c, buf[64];
do {
printf("ATTENTION: SSL/TLS certificate fingerprint mismatch.\n"
"Proceed with the connection (y/n)? ");
if (fgets(buf, sizeof(buf), stdin) == NULL)
return -1;
c = tolower((int)(*buf));
} while (c != 'y' && c != 'n');
if (c == 'y')
return 0;
else
return -1;
}
#endif /* NO_SSLTLS */