2002-11-11 13:32:04 +01:00
/*
* Sylpheed - - a GTK + based , lightweight , and fast e - mail client
* Copyright ( C ) 1999 - 2001 Hiroyuki Yamamoto
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place - Suite 330 , Boston , MA 02111 - 1307 , USA .
*/
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
2002-12-08 14:52:10 +01:00
# if USE_OPENSSL
2002-11-11 13:32:04 +01:00
# include <openssl/ssl.h>
# include <glib.h>
# include "ssl_certificate.h"
# include "utils.h"
# include "intl.h"
2002-12-08 04:21:35 +01:00
# include "log.h"
2002-12-08 16:57:35 +01:00
# include "socket.h"
2002-12-09 09:54:56 +01:00
# include "hooks.h"
2002-11-11 13:32:04 +01:00
2002-11-25 16:21:47 +01:00
static SSLCertificate * ssl_certificate_new_lookup ( X509 * x509_cert , gchar * host , gushort port , gboolean lookup ) ;
2002-11-11 13:32:04 +01:00
2002-11-12 13:38:52 +01:00
static char * get_fqdn ( char * host )
{
struct hostent * hp ;
2002-11-25 16:21:47 +01:00
if ( host = = NULL | | strlen ( host ) = = 0 )
return g_strdup ( " " ) ;
2002-11-12 13:38:52 +01:00
hp = my_gethostbyname ( host ) ;
if ( hp = = NULL )
return g_strdup ( host ) ; /*caller should free*/
else
return g_strdup ( hp - > h_name ) ;
}
2002-12-08 04:15:44 +01:00
char * readable_fingerprint ( unsigned char * src , int len )
2002-11-11 13:32:04 +01:00
{
int i = 0 ;
char * ret ;
if ( src = = NULL )
return NULL ;
ret = g_strdup ( " " ) ;
2002-11-11 22:22:58 +01:00
while ( i < len ) {
2002-11-11 13:32:04 +01:00
char * tmp2 ;
if ( i > 0 )
tmp2 = g_strdup_printf ( " %s:%02X " , ret , src [ i ] ) ;
else
tmp2 = g_strdup_printf ( " %02X " , src [ i ] ) ;
2002-11-11 22:22:58 +01:00
g_free ( ret ) ;
2002-11-11 13:32:04 +01:00
ret = g_strdup ( tmp2 ) ;
g_free ( tmp2 ) ;
i + + ;
}
return ret ;
}
2002-11-12 11:20:22 +01:00
SSLCertificate * ssl_certificate_new ( X509 * x509_cert , gchar * host , gushort port )
2002-11-25 16:21:47 +01:00
{
return ssl_certificate_new_lookup ( x509_cert , host , port , TRUE ) ;
}
static SSLCertificate * ssl_certificate_new_lookup ( X509 * x509_cert , gchar * host , gushort port , gboolean lookup )
2002-11-11 13:32:04 +01:00
{
SSLCertificate * cert = g_new0 ( SSLCertificate , 1 ) ;
2002-11-11 22:22:58 +01:00
if ( host = = NULL | | x509_cert = = NULL ) {
2002-11-11 13:32:04 +01:00
ssl_certificate_destroy ( cert ) ;
return NULL ;
}
2002-11-11 22:22:58 +01:00
cert - > x509_cert = X509_dup ( x509_cert ) ;
2002-11-25 16:21:47 +01:00
if ( lookup )
cert - > host = get_fqdn ( host ) ;
else
cert - > host = g_strdup ( host ) ;
2002-11-12 11:20:22 +01:00
cert - > port = port ;
2002-11-11 13:32:04 +01:00
return cert ;
}
static void ssl_certificate_save ( SSLCertificate * cert )
{
2002-11-12 11:20:22 +01:00
gchar * file , * port ;
2002-11-11 13:32:04 +01:00
FILE * fp ;
2002-11-12 11:20:22 +01:00
2002-11-11 13:32:04 +01:00
file = g_strconcat ( get_rc_dir ( ) , G_DIR_SEPARATOR_S ,
" certs " , G_DIR_SEPARATOR_S , NULL ) ;
if ( ! is_dir_exist ( file ) )
make_dir_hier ( file ) ;
g_free ( file ) ;
2002-11-12 11:20:22 +01:00
port = g_strdup_printf ( " %d " , cert - > port ) ;
2002-11-11 13:32:04 +01:00
file = g_strconcat ( get_rc_dir ( ) , G_DIR_SEPARATOR_S ,
" certs " , G_DIR_SEPARATOR_S ,
2002-11-12 11:20:22 +01:00
cert - > host , " . " , port , " .cert " , NULL ) ;
2002-11-11 13:32:04 +01:00
2002-11-12 11:20:22 +01:00
g_free ( port ) ;
2002-11-14 13:34:47 +01:00
fp = fopen ( file , " wb " ) ;
2002-11-11 13:32:04 +01:00
if ( fp = = NULL ) {
g_free ( file ) ;
2002-12-08 04:15:44 +01:00
debug_print ( " Can't save certificate ! \n " ) ;
2002-11-11 13:32:04 +01:00
return ;
}
2002-11-11 22:22:58 +01:00
i2d_X509_fp ( fp , cert - > x509_cert ) ;
g_free ( file ) ;
2002-11-11 13:32:04 +01:00
fclose ( fp ) ;
2002-11-11 22:22:58 +01:00
2002-11-11 13:32:04 +01:00
}
2002-11-12 13:38:52 +01:00
char * ssl_certificate_to_string ( SSLCertificate * cert )
2002-11-11 13:32:04 +01:00
{
2002-11-12 09:37:34 +01:00
char * ret , buf [ 100 ] ;
char * issuer_commonname , * issuer_location , * issuer_organization ;
char * subject_commonname , * subject_location , * subject_organization ;
char * fingerprint , * sig_status ;
2002-11-11 22:22:58 +01:00
unsigned int n ;
unsigned char md [ EVP_MAX_MD_SIZE ] ;
2002-11-12 09:37:34 +01:00
/* issuer */
2002-11-16 15:47:31 +01:00
if ( X509_NAME_get_text_by_NID ( X509_get_issuer_name ( cert - > x509_cert ) ,
NID_commonName , buf , 100 ) > = 0 )
issuer_commonname = g_strdup ( buf ) ;
else
issuer_commonname = g_strdup ( _ ( " <not in certificate> " ) ) ;
if ( X509_NAME_get_text_by_NID ( X509_get_issuer_name ( cert - > x509_cert ) ,
NID_localityName , buf , 100 ) > = 0 ) {
issuer_location = g_strdup ( buf ) ;
if ( X509_NAME_get_text_by_NID ( X509_get_issuer_name ( cert - > x509_cert ) ,
2002-11-16 16:03:51 +01:00
NID_countryName , buf , 100 ) > = 0 )
2002-11-16 15:47:31 +01:00
issuer_location = g_strconcat ( issuer_location , " , " , buf , NULL ) ;
} else if ( X509_NAME_get_text_by_NID ( X509_get_issuer_name ( cert - > x509_cert ) ,
NID_countryName , buf , 100 ) > = 0 )
issuer_location = g_strdup ( buf ) ;
else
issuer_location = g_strdup ( _ ( " <not in certificate> " ) ) ;
if ( X509_NAME_get_text_by_NID ( X509_get_issuer_name ( cert - > x509_cert ) ,
NID_organizationName , buf , 100 ) > = 0 )
issuer_organization = g_strdup ( buf ) ;
else
issuer_organization = g_strdup ( _ ( " <not in certificate> " ) ) ;
/* subject */
if ( X509_NAME_get_text_by_NID ( X509_get_subject_name ( cert - > x509_cert ) ,
NID_commonName , buf , 100 ) > = 0 )
subject_commonname = g_strdup ( buf ) ;
else
subject_commonname = g_strdup ( _ ( " <not in certificate> " ) ) ;
if ( X509_NAME_get_text_by_NID ( X509_get_subject_name ( cert - > x509_cert ) ,
NID_localityName , buf , 100 ) > = 0 ) {
subject_location = g_strdup ( buf ) ;
if ( X509_NAME_get_text_by_NID ( X509_get_subject_name ( cert - > x509_cert ) ,
NID_countryName , buf , 100 ) > = 0 )
subject_location = g_strconcat ( subject_location , " , " , buf , NULL ) ;
} else if ( X509_NAME_get_text_by_NID ( X509_get_subject_name ( cert - > x509_cert ) ,
NID_countryName , buf , 100 ) > = 0 )
subject_location = g_strdup ( buf ) ;
else
subject_location = g_strdup ( _ ( " <not in certificate> " ) ) ;
if ( X509_NAME_get_text_by_NID ( X509_get_subject_name ( cert - > x509_cert ) ,
NID_organizationName , buf , 100 ) > = 0 )
subject_organization = g_strdup ( buf ) ;
else
subject_organization = g_strdup ( _ ( " <not in certificate> " ) ) ;
2002-11-12 09:37:34 +01:00
/* fingerprint */
2002-11-11 22:22:58 +01:00
X509_digest ( cert - > x509_cert , EVP_md5 ( ) , md , & n ) ;
2002-11-12 09:37:34 +01:00
fingerprint = readable_fingerprint ( md , ( int ) n ) ;
/* signature */
sig_status = ssl_certificate_check_signer ( cert - > x509_cert ) ;
ret = g_strdup_printf ( _ ( " Owner: %s (%s) in %s \n Signed by: %s (%s) in %s \n Fingerprint: %s \n Signature status: %s " ) ,
subject_commonname , subject_organization , subject_location ,
issuer_commonname , issuer_organization , issuer_location ,
fingerprint ,
( sig_status = = NULL ? " correct " : sig_status ) ) ;
if ( issuer_commonname )
g_free ( issuer_commonname ) ;
if ( issuer_location )
g_free ( issuer_location ) ;
if ( issuer_organization )
g_free ( issuer_organization ) ;
if ( subject_commonname )
g_free ( subject_commonname ) ;
if ( subject_location )
g_free ( subject_location ) ;
if ( subject_organization )
g_free ( subject_organization ) ;
2002-11-11 22:31:23 +01:00
if ( fingerprint )
g_free ( fingerprint ) ;
2002-11-12 09:37:34 +01:00
if ( sig_status )
g_free ( sig_status ) ;
2002-11-11 13:32:04 +01:00
return ret ;
}
void ssl_certificate_destroy ( SSLCertificate * cert )
{
2002-11-25 16:21:47 +01:00
if ( cert = = NULL )
return ;
2002-11-11 22:22:58 +01:00
if ( cert - > x509_cert )
X509_free ( cert - > x509_cert ) ;
2002-11-11 13:32:04 +01:00
if ( cert - > host )
g_free ( cert - > host ) ;
g_free ( cert ) ;
cert = NULL ;
}
2002-11-25 16:21:47 +01:00
void ssl_certificate_delete_from_disk ( SSLCertificate * cert )
{
gchar * buf ;
gchar * file ;
buf = g_strdup_printf ( " %d " , cert - > port ) ;
file = g_strconcat ( get_rc_dir ( ) , G_DIR_SEPARATOR_S ,
" certs " , G_DIR_SEPARATOR_S ,
cert - > host , " . " , buf , " .cert " , NULL ) ;
unlink ( file ) ;
g_free ( buf ) ;
g_free ( file ) ;
}
2002-11-12 13:38:52 +01:00
SSLCertificate * ssl_certificate_find ( gchar * host , gushort port )
2002-11-25 16:21:47 +01:00
{
return ssl_certificate_find_lookup ( host , port , TRUE ) ;
}
SSLCertificate * ssl_certificate_find_lookup ( gchar * host , gushort port , gboolean lookup )
2002-11-11 13:32:04 +01:00
{
gchar * file ;
2002-11-12 11:20:22 +01:00
gchar * buf ;
2002-11-12 13:38:52 +01:00
gchar * fqdn_host ;
2002-11-11 22:22:58 +01:00
SSLCertificate * cert = NULL ;
X509 * tmp_x509 ;
2002-11-11 13:32:04 +01:00
FILE * fp ;
2002-11-12 13:38:52 +01:00
2002-11-25 16:21:47 +01:00
if ( lookup )
fqdn_host = get_fqdn ( host ) ;
else
fqdn_host = g_strdup ( host ) ;
2002-11-12 11:20:22 +01:00
buf = g_strdup_printf ( " %d " , port ) ;
2002-11-11 13:32:04 +01:00
file = g_strconcat ( get_rc_dir ( ) , G_DIR_SEPARATOR_S ,
" certs " , G_DIR_SEPARATOR_S ,
2002-11-12 13:38:52 +01:00
fqdn_host , " . " , buf , " .cert " , NULL ) ;
2002-11-11 13:32:04 +01:00
2002-11-12 11:20:22 +01:00
g_free ( buf ) ;
2002-11-14 13:34:47 +01:00
fp = fopen ( file , " rb " ) ;
2002-11-11 13:32:04 +01:00
if ( fp = = NULL ) {
g_free ( file ) ;
2002-11-12 13:38:52 +01:00
g_free ( fqdn_host ) ;
2002-11-11 13:32:04 +01:00
return NULL ;
}
2002-11-11 22:22:58 +01:00
if ( ( tmp_x509 = d2i_X509_fp ( fp , 0 ) ) ! = NULL ) {
2002-11-25 16:21:47 +01:00
cert = ssl_certificate_new_lookup ( tmp_x509 , fqdn_host , port , lookup ) ;
2002-11-11 22:22:58 +01:00
X509_free ( tmp_x509 ) ;
}
fclose ( fp ) ;
2002-11-11 13:32:04 +01:00
g_free ( file ) ;
2002-11-12 13:38:52 +01:00
g_free ( fqdn_host ) ;
2002-11-11 13:32:04 +01:00
return cert ;
}
static gboolean ssl_certificate_compare ( SSLCertificate * cert_a , SSLCertificate * cert_b )
{
2002-11-11 22:22:58 +01:00
if ( cert_a = = NULL | | cert_b = = NULL )
return FALSE ;
else if ( ! X509_cmp ( cert_a - > x509_cert , cert_b - > x509_cert ) )
2002-11-11 13:32:04 +01:00
return TRUE ;
else
return FALSE ;
}
2002-12-08 04:15:44 +01:00
char * ssl_certificate_check_signer ( X509 * cert )
2002-11-11 13:32:04 +01:00
{
X509_STORE_CTX store_ctx ;
X509_STORE * store ;
int ok = 0 ;
char * err_msg = NULL ;
store = X509_STORE_new ( ) ;
if ( store = = NULL ) {
printf ( " Can't create X509_STORE \n " ) ;
2002-11-12 09:37:34 +01:00
return NULL ;
2002-11-11 13:32:04 +01:00
}
2002-12-09 14:25:33 +01:00
if ( ! X509_STORE_set_default_paths ( store ) ) {
2002-11-11 13:32:04 +01:00
X509_STORE_free ( store ) ;
return g_strdup ( _ ( " Can't load X509 default paths " ) ) ;
}
X509_STORE_CTX_init ( & store_ctx , store , cert , NULL ) ;
2002-12-09 14:25:33 +01:00
if ( ! X509_verify_cert ( & store_ctx ) ) {
2002-11-11 13:32:04 +01:00
err_msg = g_strdup ( X509_verify_cert_error_string (
X509_STORE_CTX_get_error ( & store_ctx ) ) ) ;
debug_print ( " Can't check signer: %s \n " , err_msg ) ;
X509_STORE_CTX_cleanup ( & store_ctx ) ;
X509_STORE_free ( store ) ;
return err_msg ;
}
X509_STORE_CTX_cleanup ( & store_ctx ) ;
X509_STORE_free ( store ) ;
return NULL ;
}
2002-11-12 11:20:22 +01:00
gboolean ssl_certificate_check ( X509 * x509_cert , gchar * host , gushort port )
2002-11-11 13:32:04 +01:00
{
2002-11-12 11:20:22 +01:00
SSLCertificate * current_cert = ssl_certificate_new ( x509_cert , host , port ) ;
2002-11-11 13:32:04 +01:00
SSLCertificate * known_cert ;
2002-12-09 09:54:56 +01:00
SSLCertHookData cert_hook_data ;
2002-11-11 13:32:04 +01:00
if ( current_cert = = NULL ) {
debug_print ( " Buggy certificate ! \n " ) ;
return FALSE ;
}
2002-11-12 11:20:22 +01:00
known_cert = ssl_certificate_find ( host , port ) ;
2002-11-11 13:32:04 +01:00
if ( known_cert = = NULL ) {
2002-11-25 16:21:47 +01:00
gchar * err_msg , * cur_cert_str , * sig_status ;
2002-11-11 13:32:04 +01:00
2002-11-25 16:21:47 +01:00
sig_status = ssl_certificate_check_signer ( x509_cert ) ;
2002-12-08 16:57:35 +01:00
#if 0 /* disabled pref for now */
2002-11-25 16:21:47 +01:00
if ( sig_status = = NULL & & ! prefs_common . ssl_ask_unknown_valid ) {
/* trust and accept silently if hostnames match */
char * buf ; /* don't free buf ! */
if ( X509_NAME_get_text_by_NID ( X509_get_subject_name ( x509_cert ) ,
NID_commonName , buf , 100 ) > = 0 )
if ( ! strcmp ( buf , current_cert - > host ) ) {
g_free ( sig_status ) ;
ssl_certificate_save ( current_cert ) ;
ssl_certificate_destroy ( current_cert ) ;
return TRUE ;
}
}
2002-12-08 16:57:35 +01:00
# endif
2002-11-25 16:21:47 +01:00
g_free ( sig_status ) ;
2002-11-11 13:32:04 +01:00
cur_cert_str = ssl_certificate_to_string ( current_cert ) ;
2002-11-12 09:37:34 +01:00
err_msg = g_strdup_printf ( _ ( " %s presented an unknown SSL certificate: \n %s " ) ,
2002-11-12 13:38:52 +01:00
current_cert - > host ,
2002-11-12 09:37:34 +01:00
cur_cert_str ) ;
2002-11-11 13:32:04 +01:00
g_free ( cur_cert_str ) ;
2002-11-12 09:37:34 +01:00
2002-12-08 16:57:35 +01:00
#if 0 /* disabled for now */
2002-11-12 09:37:34 +01:00
if ( prefs_common . no_recv_err_panel ) {
log_error ( _ ( " %s \n \n Mail won't be retrieved on this account until you save the certificate. \n (Uncheck the \" %s \" preference). \n " ) ,
err_msg ,
_ ( " Don't popup error dialog on receive error " ) ) ;
g_free ( err_msg ) ;
return FALSE ;
}
2002-12-08 16:57:35 +01:00
# endif
2002-12-09 09:54:56 +01:00
cert_hook_data . cert = current_cert ;
cert_hook_data . old_cert = NULL ;
cert_hook_data . accept = FALSE ;
hooks_invoke ( SSLCERT_ASK_HOOKLIST , & cert_hook_data ) ;
2002-11-11 13:32:04 +01:00
g_free ( err_msg ) ;
2002-12-09 09:54:56 +01:00
if ( ! cert_hook_data . accept ) {
2002-12-08 04:15:44 +01:00
ssl_certificate_destroy ( current_cert ) ;
return FALSE ;
} else {
ssl_certificate_save ( current_cert ) ;
ssl_certificate_destroy ( current_cert ) ;
return TRUE ;
2002-11-11 13:32:04 +01:00
}
}
else if ( ! ssl_certificate_compare ( current_cert , known_cert ) ) {
2002-11-25 16:21:47 +01:00
gchar * err_msg , * known_cert_str , * cur_cert_str ;
2002-11-11 13:32:04 +01:00
known_cert_str = ssl_certificate_to_string ( known_cert ) ;
cur_cert_str = ssl_certificate_to_string ( current_cert ) ;
2002-11-12 09:37:34 +01:00
err_msg = g_strdup_printf ( _ ( " %s's SSL certificate changed ! \n We have saved this one: \n %s \n \n It is now: \n %s \n \n This could mean the server answering is not the known one. " ) ,
2002-11-12 13:38:52 +01:00
current_cert - > host ,
2002-11-11 13:32:04 +01:00
known_cert_str ,
2002-11-12 09:37:34 +01:00
cur_cert_str ) ;
2002-11-11 13:32:04 +01:00
g_free ( cur_cert_str ) ;
g_free ( known_cert_str ) ;
2002-12-08 16:57:35 +01:00
#if 0
2002-11-12 09:37:34 +01:00
if ( prefs_common . no_recv_err_panel ) {
log_error ( _ ( " %s \n \n Mail won't be retrieved on this account until you save the certificate. \n (Uncheck the \" %s \" preference). \n " ) ,
err_msg ,
_ ( " Don't popup error dialog on receive error " ) ) ;
g_free ( err_msg ) ;
return FALSE ;
}
2002-12-08 16:57:35 +01:00
# endif
2002-12-09 09:54:56 +01:00
cert_hook_data . cert = current_cert ;
cert_hook_data . old_cert = known_cert ;
cert_hook_data . accept = FALSE ;
hooks_invoke ( SSLCERT_ASK_HOOKLIST , & cert_hook_data ) ;
2002-12-08 16:57:35 +01:00
2002-11-11 13:32:04 +01:00
g_free ( err_msg ) ;
2002-12-09 09:54:56 +01:00
if ( ! cert_hook_data . accept ) {
2002-12-08 04:15:44 +01:00
ssl_certificate_destroy ( current_cert ) ;
ssl_certificate_destroy ( known_cert ) ;
return FALSE ;
} else {
ssl_certificate_save ( current_cert ) ;
ssl_certificate_destroy ( current_cert ) ;
ssl_certificate_destroy ( known_cert ) ;
return TRUE ;
2002-11-11 13:32:04 +01:00
}
}
ssl_certificate_destroy ( current_cert ) ;
ssl_certificate_destroy ( known_cert ) ;
return TRUE ;
}
2002-12-08 14:52:10 +01:00
# endif /* USE_OPENSSL */