|
Packit |
8f70b4 |
/*
|
|
Packit |
8f70b4 |
* lftp - file transfer program
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* Copyright (c) 1996-2017 by Alexander V. Lukyanov (lav@yars.free.net)
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* This program is free software; you can redistribute it and/or modify
|
|
Packit |
8f70b4 |
* it under the terms of the GNU General Public License as published by
|
|
Packit |
8f70b4 |
* the Free Software Foundation; either version 3 of the License, or
|
|
Packit |
8f70b4 |
* (at your option) any later version.
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* This program is distributed in the hope that it will be useful,
|
|
Packit |
8f70b4 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
8f70b4 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
Packit |
8f70b4 |
* GNU General Public License for more details.
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* You should have received a copy of the GNU General Public License
|
|
Packit |
8f70b4 |
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#include <config.h>
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#if USE_SSL
|
|
Packit |
8f70b4 |
#include <sys/types.h>
|
|
Packit |
8f70b4 |
#include <sys/mman.h>
|
|
Packit |
8f70b4 |
#include <sys/stat.h>
|
|
Packit |
8f70b4 |
#include <unistd.h>
|
|
Packit |
8f70b4 |
#include <fcntl.h>
|
|
Packit |
8f70b4 |
#include <errno.h>
|
|
Packit |
8f70b4 |
#include <string.h>
|
|
Packit |
8f70b4 |
#include "lftp_ssl.h"
|
|
Packit |
8f70b4 |
#include "xmalloc.h"
|
|
Packit |
8f70b4 |
#include "ResMgr.h"
|
|
Packit |
8f70b4 |
#include "log.h"
|
|
Packit |
8f70b4 |
#include "misc.h"
|
|
Packit |
8f70b4 |
#include "network.h"
|
|
Packit |
8f70b4 |
#include "buffer.h"
|
|
Packit |
8f70b4 |
extern "C" {
|
|
Packit |
8f70b4 |
#include "c-ctype.h"
|
|
Packit |
8f70b4 |
#include "quotearg.h"
|
|
Packit |
8f70b4 |
#include "quote.h"
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
lftp_ssl_base::lftp_ssl_base(int fd1,handshake_mode_t m,const char *h)
|
|
Packit |
8f70b4 |
: hostname(h)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
fd=fd1;
|
|
Packit |
8f70b4 |
handshake_done=false;
|
|
Packit |
8f70b4 |
handshake_mode=m;
|
|
Packit |
8f70b4 |
fatal=false;
|
|
Packit |
8f70b4 |
cert_error=false;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
void lftp_ssl_base::set_error(const char *s1,const char *s2)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(s2)
|
|
Packit |
8f70b4 |
error.vset(s1,": ",s2,NULL);
|
|
Packit |
8f70b4 |
else
|
|
Packit |
8f70b4 |
error.set(s1);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
void lftp_ssl_base::set_cert_error(const char *s,const xstring& fp)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
bool verify_default=ResMgr::QueryBool("ssl:verify-certificate",hostname);
|
|
Packit |
8f70b4 |
bool verify=ResMgr::QueryBool("ssl:verify-certificate",hostname);
|
|
Packit |
8f70b4 |
xstring fp_hex;
|
|
Packit |
8f70b4 |
if(fp) {
|
|
Packit |
8f70b4 |
for(unsigned i=0; i
|
|
Packit |
8f70b4 |
fp_hex.appendf("%02X:",(unsigned char)fp[i]);
|
|
Packit |
8f70b4 |
fp_hex.chomp(':');
|
|
Packit |
8f70b4 |
if(verify && verify_default)
|
|
Packit |
8f70b4 |
verify=ResMgr::QueryBool("ssl:verify-certificate",fp_hex);
|
|
Packit |
8f70b4 |
s=xstring::format("%s (%s)",s,fp_hex.get());
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
const char *const warn=verify?"ERROR":"WARNING";
|
|
Packit |
8f70b4 |
Log::global->Format(0,"%s: Certificate verification: %s\n",warn,s);
|
|
Packit |
8f70b4 |
if(verify && !error)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
set_error("Certificate verification",s);
|
|
Packit |
8f70b4 |
fatal=true;
|
|
Packit |
8f70b4 |
cert_error=true;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#if USE_GNUTLS
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Helper functions to load a certificate and key
|
|
Packit |
8f70b4 |
* files into memory. They use mmap for simplicity.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
static gnutls_datum_t mmap_file(const char *file)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
int fd;
|
|
Packit |
8f70b4 |
gnutls_datum_t mmaped_file = { NULL, 0 };
|
|
Packit |
8f70b4 |
struct stat stat_st;
|
|
Packit |
8f70b4 |
void *ptr;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
fd = open(file, 0);
|
|
Packit |
8f70b4 |
if (fd == -1)
|
|
Packit |
8f70b4 |
return mmaped_file;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
fstat(fd, &stat_st);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if ((ptr =
|
|
Packit |
8f70b4 |
mmap(NULL, stat_st.st_size, PROT_READ, MAP_SHARED, fd,
|
|
Packit |
8f70b4 |
0)) == MAP_FAILED)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
close(fd);
|
|
Packit |
8f70b4 |
return mmaped_file;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
close(fd);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
mmaped_file.data = (unsigned char*)ptr;
|
|
Packit |
8f70b4 |
mmaped_file.size = stat_st.st_size;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
return mmaped_file;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
static void munmap_file(gnutls_datum_t data)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
munmap(data.data, data.size);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#if LFTP_LIBGNUTLS_VERSION_CODE < 0x010201
|
|
Packit |
8f70b4 |
#define gnutls_x509_crt_list_import lftp_gnutls_x509_crt_list_import
|
|
Packit |
8f70b4 |
#define GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED 1
|
|
Packit |
8f70b4 |
static
|
|
Packit |
8f70b4 |
int gnutls_x509_crt_list_import(gnutls_x509_crt_t *certs, unsigned int* cert_max,
|
|
Packit |
8f70b4 |
const gnutls_datum_t * data, gnutls_x509_crt_fmt_t format, unsigned int flags);
|
|
Packit |
8f70b4 |
#endif
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
void lftp_ssl_gnutls_instance::LoadCA()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
// free CA first
|
|
Packit |
8f70b4 |
for(unsigned i=0; i
|
|
Packit |
8f70b4 |
gnutls_x509_crt_deinit(ca_list[i]);
|
|
Packit |
8f70b4 |
xfree(ca_list);
|
|
Packit |
8f70b4 |
ca_list=0;
|
|
Packit |
8f70b4 |
ca_list_size=0;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
const char *ca_file=ResMgr::Query("ssl:ca-file",0);
|
|
Packit |
8f70b4 |
if(!ca_file || !ca_file[0])
|
|
Packit |
8f70b4 |
return;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
gnutls_datum_t ca_pem=mmap_file(ca_file);
|
|
Packit |
8f70b4 |
if(!ca_pem.data)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
Log::global->Format(0,"%s: %s\n",ca_file,strerror(errno));
|
|
Packit |
8f70b4 |
return;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
ca_list_size=64;
|
|
Packit |
8f70b4 |
ca_list=(gnutls_x509_crt_t*)xmalloc(ca_list_size*sizeof(gnutls_x509_crl_t));
|
|
Packit |
8f70b4 |
int res=gnutls_x509_crt_list_import(ca_list,&ca_list_size,&ca_pem,GNUTLS_X509_FMT_PEM,GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
|
|
Packit |
8f70b4 |
if(res==GNUTLS_E_SHORT_MEMORY_BUFFER)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
ca_list=(gnutls_x509_crt_t*)xrealloc(ca_list,ca_list_size*sizeof(gnutls_x509_crl_t));
|
|
Packit |
8f70b4 |
res=gnutls_x509_crt_list_import(ca_list,&ca_list_size,&ca_pem,GNUTLS_X509_FMT_PEM,0);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
if(res<0)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
Log::global->Format(0,"gnutls_x509_crt_list_import: %s\n",gnutls_strerror(res));
|
|
Packit |
8f70b4 |
xfree(ca_list);
|
|
Packit |
8f70b4 |
ca_list=0;
|
|
Packit |
8f70b4 |
ca_list_size=0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
munmap_file(ca_pem);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
void lftp_ssl_gnutls_instance::LoadCRL()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
// free CRL first
|
|
Packit |
8f70b4 |
for(unsigned i=0; i
|
|
Packit |
8f70b4 |
gnutls_x509_crl_deinit(crl_list[i]);
|
|
Packit |
8f70b4 |
xfree(crl_list);
|
|
Packit |
8f70b4 |
crl_list=0;
|
|
Packit |
8f70b4 |
crl_list_size=0;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
const char *crl_file=ResMgr::Query("ssl:crl-file",0);
|
|
Packit |
8f70b4 |
if(!crl_file || !crl_file[0])
|
|
Packit |
8f70b4 |
return;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
gnutls_datum_t crl_pem=mmap_file(crl_file);
|
|
Packit |
8f70b4 |
if(!crl_pem.data)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
Log::global->Format(0,"%s: %s\n",crl_file,strerror(errno));
|
|
Packit |
8f70b4 |
return;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
crl_list_size=1;
|
|
Packit |
8f70b4 |
crl_list=(gnutls_x509_crl_t*)xmalloc(crl_list_size*sizeof(gnutls_x509_crl_t));
|
|
Packit |
8f70b4 |
int res=gnutls_x509_crl_import(crl_list[0],&crl_pem,GNUTLS_X509_FMT_PEM);
|
|
Packit |
8f70b4 |
if(res<0)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
Log::global->Format(0,"gnutls_x509_crl_import: %s\n",gnutls_strerror(res));
|
|
Packit |
8f70b4 |
xfree(crl_list);
|
|
Packit |
8f70b4 |
crl_list=0;
|
|
Packit |
8f70b4 |
crl_list_size=0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
munmap_file(crl_pem);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
void lftp_ssl_gnutls_instance::Reconfig(const char *name)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(!name || !strcmp(name,"ssl:ca-file"))
|
|
Packit |
8f70b4 |
LoadCA();
|
|
Packit |
8f70b4 |
if(!name || !strcmp(name,"ssl:crl-file"))
|
|
Packit |
8f70b4 |
LoadCRL();
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
static const char *lftp_ssl_find_ca_file()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
// a few possible locations of ca-bundle.crt
|
|
Packit |
8f70b4 |
static const char *const ca_file_location[]={
|
|
Packit |
8f70b4 |
"/etc/pki/tls/certs/ca-bundle.crt",
|
|
Packit |
8f70b4 |
"/etc/certs/ca-bundle.crt",
|
|
Packit |
8f70b4 |
"/usr/share/ssl/certs/ca-bundle.crt",
|
|
Packit |
8f70b4 |
"/etc/ssl/certs/ca-certificates.crt",
|
|
Packit |
8f70b4 |
"/usr/local/ssl/certs/ca-bundle.crt",
|
|
Packit |
8f70b4 |
"/etc/apache/ssl.crt/ca-bundle.crt",
|
|
Packit |
8f70b4 |
"/usr/share/curl/curl-ca-bundle.crt",
|
|
Packit |
8f70b4 |
0};
|
|
Packit |
8f70b4 |
for(int i=0; ca_file_location[i]; i++)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(access(ca_file_location[i], R_OK)==0)
|
|
Packit |
8f70b4 |
return ca_file_location[i];
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
return 0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
static void lftp_ssl_gnutls_log_func(int level, const char *msg)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(!strncmp(msg,"ASSERT",6)
|
|
Packit |
8f70b4 |
|| !strncmp(msg,"READ",4)
|
|
Packit |
8f70b4 |
|| !strncmp(msg,"WRITE",5))
|
|
Packit |
8f70b4 |
level+=10;
|
|
Packit |
8f70b4 |
Log::global->Format(9+level,"GNUTLS: %s",msg);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
lftp_ssl_gnutls_instance::lftp_ssl_gnutls_instance()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
ca_list=0;
|
|
Packit |
8f70b4 |
ca_list_size=0;
|
|
Packit |
8f70b4 |
crl_list=0;
|
|
Packit |
8f70b4 |
crl_list_size=0;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
gnutls_global_init();
|
|
Packit |
8f70b4 |
gnutls_global_set_log_function(lftp_ssl_gnutls_log_func);
|
|
Packit |
8f70b4 |
gnutls_global_set_log_level(9);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
const char *ca_file=ResMgr::Query("ssl:ca-file",0);
|
|
Packit |
8f70b4 |
if(!ca_file || !ca_file[0])
|
|
Packit |
8f70b4 |
ResMgr::Set("ssl:ca-file",0,lftp_ssl_find_ca_file());
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
Reconfig(0);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
lftp_ssl_gnutls_instance::~lftp_ssl_gnutls_instance()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
gnutls_global_deinit();
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
Ref<lftp_ssl_gnutls_instance> lftp_ssl_gnutls::instance;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
void lftp_ssl_gnutls::global_init()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(!instance)
|
|
Packit |
8f70b4 |
instance=new lftp_ssl_gnutls_instance();
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
void lftp_ssl_gnutls::global_deinit()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
instance=0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#ifndef GNUTLS_NONBLOCK // for gnutls < 3.0
|
|
Packit |
8f70b4 |
# define add_GNUTLS_NONBLOCK
|
|
Packit |
8f70b4 |
# else
|
|
Packit |
8f70b4 |
# define add_GNUTLS_NONBLOCK |GNUTLS_NONBLOCK
|
|
Packit |
8f70b4 |
#endif
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
lftp_ssl_gnutls::lftp_ssl_gnutls(int fd1,handshake_mode_t m,const char *h)
|
|
Packit |
8f70b4 |
: lftp_ssl_base(fd1,m,h)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
global_init();
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
cred=0;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
gnutls_init(&session,(m==CLIENT?GNUTLS_CLIENT:GNUTLS_SERVER)add_GNUTLS_NONBLOCK);
|
|
Packit |
8f70b4 |
gnutls_set_default_priority(session);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
gnutls_transport_set_ptr(session,(gnutls_transport_ptr_t)fd);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
const char *priority=ResMgr::Query("ssl:priority", 0);
|
|
Packit |
8f70b4 |
if(!priority || !*priority)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
// hack for some ftp servers
|
|
Packit |
8f70b4 |
const char *auth=ResMgr::Query("ftp:ssl-auth", hostname);
|
|
Packit |
8f70b4 |
if(auth && !strncmp(auth, "SSL", 3))
|
|
Packit |
8f70b4 |
priority="NORMAL:+VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.2";
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
if(priority && *priority)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
int res = gnutls_priority_set_direct(session, priority, 0);
|
|
Packit |
8f70b4 |
if(res != GNUTLS_E_SUCCESS)
|
|
Packit |
8f70b4 |
Log::global->Format(0,"gnutls_priority_set_direct(`%s'): %s\n",priority,gnutls_strerror(res));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(h && ResMgr::QueryBool("ssl:use-sni",h)) {
|
|
Packit |
8f70b4 |
if(gnutls_server_name_set(session, GNUTLS_NAME_DNS, h, xstrlen(h)) < 0)
|
|
Packit |
8f70b4 |
fprintf(stderr,"WARNING: failed to configure server name indication (SNI) TLS extension\n");
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
void lftp_ssl_gnutls::load_keys()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
gnutls_certificate_allocate_credentials(&cred);
|
|
Packit |
8f70b4 |
int res;
|
|
Packit |
8f70b4 |
#if 0
|
|
Packit |
8f70b4 |
const char *ca_file=ResMgr::Query("ssl:ca-file",hostname);
|
|
Packit |
8f70b4 |
const char *ca_file0=ResMgr::Query("ssl:ca-file",0);
|
|
Packit |
8f70b4 |
if(ca_file && ca_file[0] && xstrcmp(ca_file,ca_file0))
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
res=gnutls_certificate_set_x509_trust_file(cred,ca_file,GNUTLS_X509_FMT_PEM);
|
|
Packit |
8f70b4 |
if(res<0)
|
|
Packit |
8f70b4 |
Log::global->Format(0,"gnutls_certificate_set_x509_trust_file(%s): %s\n",ca_file,gnutls_strerror(res));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
const char *crl_file=ResMgr::Query("ssl:crl-file",hostname);
|
|
Packit |
8f70b4 |
const char *crl_file0=ResMgr::Query("ssl:crl-file",0);
|
|
Packit |
8f70b4 |
if(crl_file && crl_file[0] && xstrcmp(crl_file,crl_file0))
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
res=gnutls_certificate_set_x509_crl_file(cred,crl_file,GNUTLS_X509_FMT_PEM);
|
|
Packit |
8f70b4 |
if(res<0)
|
|
Packit |
8f70b4 |
Log::global->Format(0,"gnutls_certificate_set_x509_crl_file(%s): %s\n",crl_file,gnutls_strerror(res));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
#endif
|
|
Packit |
8f70b4 |
const char *key_file =ResMgr::Query("ssl:key-file",hostname);
|
|
Packit |
8f70b4 |
const char *cert_file=ResMgr::Query("ssl:cert-file",hostname);
|
|
Packit |
8f70b4 |
if(key_file && key_file[0] && cert_file && cert_file[0])
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
res=gnutls_certificate_set_x509_key_file(cred,cert_file,key_file,GNUTLS_X509_FMT_PEM);
|
|
Packit |
8f70b4 |
if(res<0)
|
|
Packit |
8f70b4 |
Log::global->Format(0,"gnutls_certificate_set_x509_key_file(%s,%s): %s\n",cert_file,key_file,gnutls_strerror(res));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cred);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
void lftp_ssl_gnutls::shutdown()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(handshake_done)
|
|
Packit |
8f70b4 |
gnutls_bye(session,GNUTLS_SHUT_RDWR); // FIXME - E_AGAIN
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
lftp_ssl_gnutls::~lftp_ssl_gnutls()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(cred)
|
|
Packit |
8f70b4 |
gnutls_certificate_free_credentials(cred);
|
|
Packit |
8f70b4 |
gnutls_deinit(session);
|
|
Packit |
8f70b4 |
session=0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* This function will try to verify the peer's certificate chain, and
|
|
Packit |
8f70b4 |
* also check if the hostname matches, and the activation, expiration dates.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
void lftp_ssl_gnutls::verify_certificate_chain(const gnutls_datum_t *cert_chain,int cert_chain_length)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
int i;
|
|
Packit |
8f70b4 |
gnutls_x509_crt_t *cert=(gnutls_x509_crt_t*)alloca(cert_chain_length*sizeof(gnutls_x509_crt_t));
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Import all the certificates in the chain to
|
|
Packit |
8f70b4 |
* native certificate format.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
for (i = 0; i < cert_chain_length; i++)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
gnutls_x509_crt_init(&cert[i]);
|
|
Packit |
8f70b4 |
gnutls_x509_crt_import(cert[i],&cert_chain[i],GNUTLS_X509_FMT_DER);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Now verify the certificates against their issuers
|
|
Packit |
8f70b4 |
* in the chain.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
for (i = 1; i < cert_chain_length; i++)
|
|
Packit |
8f70b4 |
verify_cert2(cert[i - 1], cert[i]);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Here we must verify the last certificate in the chain against
|
|
Packit |
8f70b4 |
* our trusted CA list.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
verify_last_cert(cert[cert_chain_length - 1]);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Check if the name in the first certificate matches our destination!
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
bool check_hostname = ResMgr::QueryBool("ssl:check-hostname", hostname);
|
|
Packit |
8f70b4 |
if(check_hostname) {
|
|
Packit |
8f70b4 |
if(!gnutls_x509_crt_check_hostname(cert[0], hostname))
|
|
Packit |
8f70b4 |
set_cert_error(xstring::format("certificate common name doesn't match requested host name %s",quote(hostname)),get_fp(cert[0]));
|
|
Packit |
8f70b4 |
} else {
|
|
Packit |
8f70b4 |
Log::global->Format(0, "WARNING: Certificate verification: hostname checking disabled\n");
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
for (i = 0; i < cert_chain_length; i++)
|
|
Packit |
8f70b4 |
gnutls_x509_crt_deinit(cert[i]);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Verifies a certificate against an other certificate
|
|
Packit |
8f70b4 |
* which is supposed to be it's issuer. Also checks the
|
|
Packit |
8f70b4 |
* crl_list if the certificate is revoked.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
void lftp_ssl_gnutls::verify_cert2(gnutls_x509_crt_t crt,gnutls_x509_crt_t issuer)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
int ret;
|
|
Packit |
8f70b4 |
time_t now = SMTask::now;
|
|
Packit |
8f70b4 |
size_t name_size;
|
|
Packit |
8f70b4 |
char name[256];
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Print information about the certificates to
|
|
Packit |
8f70b4 |
* be checked.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
name_size = sizeof(name);
|
|
Packit |
8f70b4 |
gnutls_x509_crt_get_dn(crt, name, &name_size);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
Log::global->Format(9, "Certificate: %s\n", name);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
name_size = sizeof(name);
|
|
Packit |
8f70b4 |
gnutls_x509_crt_get_issuer_dn(crt, name, &name_size);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
Log::global->Format(9, " Issued by: %s\n", name);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Get the DN of the issuer cert.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
name_size = sizeof(name);
|
|
Packit |
8f70b4 |
gnutls_x509_crt_get_dn(issuer, name, &name_size);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
Log::global->Format(9, " Checking against: %s\n", name);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Do the actual verification.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
unsigned crt_status=0;
|
|
Packit |
8f70b4 |
unsigned issuer_status=0;
|
|
Packit |
8f70b4 |
gnutls_x509_crt_verify(crt, &issuer, 1, 0, &crt_status);
|
|
Packit |
8f70b4 |
if(crt_status&GNUTLS_CERT_SIGNER_NOT_CA)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
// recheck the issuer certificate against CA
|
|
Packit |
8f70b4 |
gnutls_x509_crt_verify(issuer, instance->ca_list, instance->ca_list_size, 0, &issuer_status);
|
|
Packit |
8f70b4 |
if(issuer_status==0)
|
|
Packit |
8f70b4 |
crt_status&=~GNUTLS_CERT_SIGNER_NOT_CA;
|
|
Packit |
8f70b4 |
if(crt_status==GNUTLS_CERT_INVALID)
|
|
Packit |
8f70b4 |
crt_status=0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
if (crt_status & GNUTLS_CERT_INVALID)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
char msg[256];
|
|
Packit |
8f70b4 |
strcpy(msg,"Not trusted");
|
|
Packit |
8f70b4 |
if(crt_status & GNUTLS_CERT_SIGNER_NOT_FOUND)
|
|
Packit |
8f70b4 |
strcat(msg,": no issuer was found");
|
|
Packit |
8f70b4 |
if(crt_status & GNUTLS_CERT_SIGNER_NOT_CA)
|
|
Packit |
8f70b4 |
strcat(msg,": issuer is not a CA");
|
|
Packit |
8f70b4 |
set_cert_error(msg,get_fp(crt));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
else
|
|
Packit |
8f70b4 |
Log::global->Format(9, " Trusted\n");
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Now check the expiration dates.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
if (gnutls_x509_crt_get_activation_time(crt) > now)
|
|
Packit |
8f70b4 |
set_cert_error("Not yet activated",get_fp(crt));
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if (gnutls_x509_crt_get_expiration_time(crt) < now)
|
|
Packit |
8f70b4 |
set_cert_error("Expired",get_fp(crt));
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Check if the certificate is revoked.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
ret = gnutls_x509_crt_check_revocation(crt, instance->crl_list, instance->crl_list_size);
|
|
Packit |
8f70b4 |
if (ret == 1) { /* revoked */
|
|
Packit |
8f70b4 |
set_cert_error("Revoked",get_fp(crt));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Verifies a certificate against the trusted CA list.
|
|
Packit |
8f70b4 |
* Also checks the crl_list if the certificate is revoked.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
void lftp_ssl_gnutls::verify_last_cert(gnutls_x509_crt_t crt)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
unsigned int crt_status;
|
|
Packit |
8f70b4 |
int ret;
|
|
Packit |
8f70b4 |
time_t now = SMTask::now;
|
|
Packit |
8f70b4 |
size_t name_size;
|
|
Packit |
8f70b4 |
char name[256];
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Print information about the certificates to
|
|
Packit |
8f70b4 |
* be checked.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
name_size = sizeof(name);
|
|
Packit |
8f70b4 |
gnutls_x509_crt_get_dn(crt, name, &name_size);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
Log::global->Format(9, "Certificate: %s\n", name);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
name_size = sizeof(name);
|
|
Packit |
8f70b4 |
gnutls_x509_crt_get_issuer_dn(crt, name, &name_size);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
Log::global->Format(9, " Issued by: %s\n", name);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Do the actual verification.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
gnutls_x509_crt_verify(crt, instance->ca_list, instance->ca_list_size, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT, &crt_status);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if (crt_status & GNUTLS_CERT_INVALID)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
char msg[256];
|
|
Packit |
8f70b4 |
strcpy(msg,"Not trusted");
|
|
Packit |
8f70b4 |
if (crt_status & GNUTLS_CERT_SIGNER_NOT_CA)
|
|
Packit |
8f70b4 |
strcat(msg,": Issuer is not a CA");
|
|
Packit |
8f70b4 |
set_cert_error(msg,get_fp(crt));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
else
|
|
Packit |
8f70b4 |
Log::global->Format(9, " Trusted\n");
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Now check the expiration dates.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
if(gnutls_x509_crt_get_activation_time(crt) > now)
|
|
Packit |
8f70b4 |
set_cert_error("Not yet activated",get_fp(crt));
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(gnutls_x509_crt_get_expiration_time(crt) < now)
|
|
Packit |
8f70b4 |
set_cert_error("Expired",get_fp(crt));
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Check if the certificate is revoked.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
ret = gnutls_x509_crt_check_revocation(crt, instance->crl_list, instance->crl_list_size);
|
|
Packit |
8f70b4 |
if (ret == 1) { /* revoked */
|
|
Packit |
8f70b4 |
set_cert_error("Revoked",get_fp(crt));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
bool lftp_ssl_gnutls::check_fatal(int res)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(!gnutls_error_is_fatal(res))
|
|
Packit |
8f70b4 |
return false;
|
|
Packit |
8f70b4 |
if((res==GNUTLS_E_UNEXPECTED_PACKET_LENGTH
|
|
Packit |
8f70b4 |
|| res==GNUTLS_E_PUSH_ERROR || res==GNUTLS_E_PULL_ERROR
|
|
Packit |
8f70b4 |
|| res==GNUTLS_E_DECRYPTION_FAILED)
|
|
Packit |
8f70b4 |
&& (!errno || temporary_network_error(errno)))
|
|
Packit |
8f70b4 |
return false;
|
|
Packit |
8f70b4 |
return true;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
int lftp_ssl_gnutls::do_handshake()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(handshake_done)
|
|
Packit |
8f70b4 |
return DONE;
|
|
Packit |
8f70b4 |
errno=0;
|
|
Packit |
8f70b4 |
int res=gnutls_handshake(session);
|
|
Packit |
8f70b4 |
if(res<0)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(res==GNUTLS_E_AGAIN || res==GNUTLS_E_INTERRUPTED)
|
|
Packit |
8f70b4 |
return RETRY;
|
|
Packit |
8f70b4 |
else // error
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
fatal=check_fatal(res);
|
|
Packit |
8f70b4 |
set_error("gnutls_handshake",gnutls_strerror(res));
|
|
Packit |
8f70b4 |
return ERROR;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
handshake_done=true;
|
|
Packit |
8f70b4 |
SMTask::current->Timeout(0);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(gnutls_certificate_type_get(session)!=GNUTLS_CRT_X509)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
set_cert_error("Unsupported certificate type",xstring::null);
|
|
Packit |
8f70b4 |
return DONE; // FIXME: handle openpgp as well
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
unsigned cert_list_size=0;
|
|
Packit |
8f70b4 |
const gnutls_datum_t *cert_list=gnutls_certificate_get_peers(session,&cert_list_size);
|
|
Packit |
8f70b4 |
if(cert_list==NULL || cert_list_size==0)
|
|
Packit |
8f70b4 |
set_cert_error("No certificate was found!",xstring::null);
|
|
Packit |
8f70b4 |
else
|
|
Packit |
8f70b4 |
verify_certificate_chain(cert_list,cert_list_size);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
return DONE;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#ifndef GNUTLS_E_PREMATURE_TERMINATION // for gnutls < 3.0
|
|
Packit |
8f70b4 |
# define GNUTLS_E_PREMATURE_TERMINATION GNUTLS_E_UNEXPECTED_PACKET_LENGTH
|
|
Packit |
8f70b4 |
#endif
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
int lftp_ssl_gnutls::read(char *buf,int size)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(error)
|
|
Packit |
8f70b4 |
return ERROR;
|
|
Packit |
8f70b4 |
int res=do_handshake();
|
|
Packit |
8f70b4 |
if(res!=DONE)
|
|
Packit |
8f70b4 |
return res;
|
|
Packit |
8f70b4 |
errno=0;
|
|
Packit |
8f70b4 |
res=gnutls_record_recv(session,buf,size);
|
|
Packit |
8f70b4 |
if(res<0)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(res==GNUTLS_E_AGAIN || res==GNUTLS_E_INTERRUPTED)
|
|
Packit |
8f70b4 |
return RETRY;
|
|
Packit |
8f70b4 |
else if(res==GNUTLS_E_UNEXPECTED_PACKET_LENGTH || res==GNUTLS_E_PREMATURE_TERMINATION)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
Log::global->Format(7,"gnutls_record_recv: %s Assuming EOF.\n",gnutls_strerror(res));
|
|
Packit |
8f70b4 |
return 0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
else // error
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
fatal=check_fatal(res);
|
|
Packit |
8f70b4 |
set_error("gnutls_record_recv",gnutls_strerror(res));
|
|
Packit |
8f70b4 |
return ERROR;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
return res;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
int lftp_ssl_gnutls::write(const char *buf,int size)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(error)
|
|
Packit |
8f70b4 |
return ERROR;
|
|
Packit |
8f70b4 |
int res=do_handshake();
|
|
Packit |
8f70b4 |
if(res!=DONE)
|
|
Packit |
8f70b4 |
return res;
|
|
Packit |
8f70b4 |
if(size==0)
|
|
Packit |
8f70b4 |
return 0;
|
|
Packit |
8f70b4 |
errno=0;
|
|
Packit |
8f70b4 |
res=gnutls_record_send(session,buf,size);
|
|
Packit |
8f70b4 |
if(res<0)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(res==GNUTLS_E_AGAIN || res==GNUTLS_E_INTERRUPTED)
|
|
Packit |
8f70b4 |
return RETRY;
|
|
Packit |
8f70b4 |
else // error
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
fatal=check_fatal(res);
|
|
Packit |
8f70b4 |
set_error("gnutls_record_send",gnutls_strerror(res));
|
|
Packit |
8f70b4 |
return ERROR;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
return res;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
bool lftp_ssl_gnutls::want_in()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
return gnutls_record_get_direction(session)==0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
bool lftp_ssl_gnutls::want_out()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
return gnutls_record_get_direction(session)==1;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
void lftp_ssl_gnutls::copy_sid(const lftp_ssl_gnutls *o)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
size_t session_data_size=0;
|
|
Packit |
8f70b4 |
void *session_data;
|
|
Packit |
8f70b4 |
int res=gnutls_session_get_data(o->session,NULL,&session_data_size);
|
|
Packit |
8f70b4 |
if(res!=GNUTLS_E_SUCCESS && res!=GNUTLS_E_SHORT_MEMORY_BUFFER)
|
|
Packit |
8f70b4 |
return;
|
|
Packit |
8f70b4 |
session_data=xmalloc(session_data_size);
|
|
Packit |
8f70b4 |
if(gnutls_session_get_data(o->session,session_data,&session_data_size)!=GNUTLS_E_SUCCESS)
|
|
Packit |
8f70b4 |
return;
|
|
Packit |
8f70b4 |
gnutls_session_set_data(session,session_data,session_data_size);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#include <sha1.h>
|
|
Packit |
8f70b4 |
const xstring& lftp_ssl_gnutls::get_fp(gnutls_x509_crt_t cert)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
static xstring fp;
|
|
Packit |
8f70b4 |
fp.truncate();
|
|
Packit |
8f70b4 |
size_t fp_len=SHA1_DIGEST_SIZE;
|
|
Packit |
8f70b4 |
if(gnutls_x509_crt_get_fingerprint(cert,GNUTLS_DIG_SHA1,fp.add_space(fp_len),&fp_len))
|
|
Packit |
8f70b4 |
return xstring::null;
|
|
Packit |
8f70b4 |
fp.add_commit(fp_len);
|
|
Packit |
8f70b4 |
return fp;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#if LFTP_LIBGNUTLS_VERSION_CODE < 0x010201
|
|
Packit |
8f70b4 |
#define PEM_CERT_SEP2 "-----BEGIN X509 CERTIFICATE"
|
|
Packit |
8f70b4 |
#define PEM_CERT_SEP "-----BEGIN CERTIFICATE"
|
|
Packit |
8f70b4 |
#define CLEAR_CERTS \
|
|
Packit |
8f70b4 |
for(j=0;j
|
|
Packit |
8f70b4 |
/**
|
|
Packit |
8f70b4 |
* gnutls_x509_crt_list_import - This function will import a PEM encoded certificate list
|
|
Packit |
8f70b4 |
* @certs: The structures to store the parsed certificate. Must not be initialized.
|
|
Packit |
8f70b4 |
* @cert_max: Initially must hold the maximum number of certs. It will be updated with the number of certs available.
|
|
Packit |
8f70b4 |
* @data: The PEM encoded certificate.
|
|
Packit |
8f70b4 |
* @format: One of DER or PEM. Only PEM is supported for now.
|
|
Packit |
8f70b4 |
* @flags: must be zero or an OR'd sequence of gnutls_certificate_import_flags.
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* This function will convert the given PEM encoded certificate list
|
|
Packit |
8f70b4 |
* to the native gnutls_x509_crt_t format. The output will be stored in @certs.
|
|
Packit |
8f70b4 |
* They will be automatically initialized.
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* If the Certificate is PEM encoded it should have a header of "X509 CERTIFICATE", or
|
|
Packit |
8f70b4 |
* "CERTIFICATE".
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* Returns the number of certificates read or a negative error value.
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
static
|
|
Packit |
8f70b4 |
int gnutls_x509_crt_list_import(gnutls_x509_crt_t *certs, unsigned int* cert_max,
|
|
Packit |
8f70b4 |
const gnutls_datum_t * data, gnutls_x509_crt_fmt_t format, unsigned int flags)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
int size;
|
|
Packit |
8f70b4 |
const char *ptr;
|
|
Packit |
8f70b4 |
gnutls_datum_t tmp;
|
|
Packit |
8f70b4 |
int ret, nocopy=0;
|
|
Packit |
8f70b4 |
unsigned int count=0,j;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* move to the certificate
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
ptr = (const char *)memmem(data->data, data->size,
|
|
Packit |
8f70b4 |
PEM_CERT_SEP, sizeof(PEM_CERT_SEP) - 1);
|
|
Packit |
8f70b4 |
if (ptr == NULL)
|
|
Packit |
8f70b4 |
ptr = (const char *)memmem(data->data, data->size,
|
|
Packit |
8f70b4 |
PEM_CERT_SEP2, sizeof(PEM_CERT_SEP2) - 1);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if (ptr == NULL) {
|
|
Packit |
8f70b4 |
return GNUTLS_E_BASE64_DECODING_ERROR;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
size = data->size - (ptr - (char*)data->data);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
count = 0;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
do {
|
|
Packit |
8f70b4 |
if (count >= *cert_max) {
|
|
Packit |
8f70b4 |
if (!(flags & GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED))
|
|
Packit |
8f70b4 |
break;
|
|
Packit |
8f70b4 |
else
|
|
Packit |
8f70b4 |
nocopy = 1;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if (!nocopy) {
|
|
Packit |
8f70b4 |
ret = gnutls_x509_crt_init( &certs[count]);
|
|
Packit |
8f70b4 |
if (ret < 0) {
|
|
Packit |
8f70b4 |
goto error;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
tmp.data = (unsigned char*)ptr;
|
|
Packit |
8f70b4 |
tmp.size = size;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
ret = gnutls_x509_crt_import( certs[count], &tmp, GNUTLS_X509_FMT_PEM);
|
|
Packit |
8f70b4 |
if (ret < 0) {
|
|
Packit |
8f70b4 |
goto error;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* now we move ptr after the pem header
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
ptr++;
|
|
Packit |
8f70b4 |
/* find the next certificate (if any)
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
size = data->size - (ptr - (char*)data->data);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if (size > 0) {
|
|
Packit |
8f70b4 |
const char *ptr2;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
ptr2 =
|
|
Packit |
8f70b4 |
(const char *)memmem(ptr, size, PEM_CERT_SEP, sizeof(PEM_CERT_SEP) - 1);
|
|
Packit |
8f70b4 |
if (ptr2 == NULL)
|
|
Packit |
8f70b4 |
ptr2 = (const char *)memmem(ptr, size, PEM_CERT_SEP2,
|
|
Packit |
8f70b4 |
sizeof(PEM_CERT_SEP2) - 1);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
ptr = ptr2;
|
|
Packit |
8f70b4 |
} else
|
|
Packit |
8f70b4 |
ptr = NULL;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
count++;
|
|
Packit |
8f70b4 |
} while (ptr != NULL);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
*cert_max = count;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if (nocopy==0)
|
|
Packit |
8f70b4 |
return count;
|
|
Packit |
8f70b4 |
else
|
|
Packit |
8f70b4 |
return GNUTLS_E_SHORT_MEMORY_BUFFER;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
error:
|
|
Packit |
8f70b4 |
CLEAR_CERTS;
|
|
Packit |
8f70b4 |
return ret;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
#endif
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/*=============================== OpenSSL ====================================*/
|
|
Packit |
8f70b4 |
#elif USE_OPENSSL
|
|
Packit |
8f70b4 |
//static int lftp_ssl_passwd_callback(char *buf,int size,int rwflag,void *userdata);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#if OPENSSL_VERSION_NUMBER < 0x10100000L || LIBRESSL_VERSION_NUMBER
|
|
Packit |
8f70b4 |
// for compatibility with older versions
|
|
Packit |
8f70b4 |
X509_OBJECT *X509_OBJECT_new()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
X509_OBJECT *ret;
|
|
Packit |
8f70b4 |
return (X509_OBJECT*)OPENSSL_malloc(sizeof(*ret));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
void X509_OBJECT_free(X509_OBJECT *a)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if (a == NULL)
|
|
Packit |
8f70b4 |
return;
|
|
Packit |
8f70b4 |
X509_OBJECT_free_contents(a);
|
|
Packit |
8f70b4 |
OPENSSL_free(a);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
X509_CRL *X509_OBJECT_get0_X509_CRL(X509_OBJECT *a)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if (a == NULL) return NULL;
|
|
Packit |
8f70b4 |
if (a->type != X509_LU_CRL) return NULL;
|
|
Packit |
8f70b4 |
return a->data.crl;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
# define X509_CRL_get0_nextUpdate(x) X509_CRL_get_nextUpdate(x)
|
|
Packit |
8f70b4 |
# define ASN1_STRING_get0_data(x) ASN1_STRING_data(x)
|
|
Packit |
8f70b4 |
# define X509_REVOKED_get0_serialNumber(x) (x->serialNumber)
|
|
Packit |
8f70b4 |
#endif // OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
Ref<lftp_ssl_openssl_instance> lftp_ssl_openssl::instance;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
static char file[256];
|
|
Packit |
8f70b4 |
static void lftp_ssl_write_rnd()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
RAND_write_file(file);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
void lftp_ssl_openssl::global_init()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(!instance)
|
|
Packit |
8f70b4 |
instance=new lftp_ssl_openssl_instance();
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
void lftp_ssl_openssl::global_deinit()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
instance=0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#ifndef SSL_OP_NO_TICKET
|
|
Packit |
8f70b4 |
# define SSL_OP_NO_TICKET 0
|
|
Packit |
8f70b4 |
#endif
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
lftp_ssl_openssl_instance::lftp_ssl_openssl_instance()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
crl_store=0;
|
|
Packit |
8f70b4 |
ssl_ctx=0;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#ifdef WINDOWS
|
|
Packit |
8f70b4 |
RAND_screen();
|
|
Packit |
8f70b4 |
#endif
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
RAND_file_name(file,sizeof(file));
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(RAND_load_file(file,-1) && RAND_status()!=0)
|
|
Packit |
8f70b4 |
atexit(lftp_ssl_write_rnd);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#if SSLEAY_VERSION_NUMBER < 0x0800
|
|
Packit |
8f70b4 |
ssl_ctx=SSL_CTX_new();
|
|
Packit |
8f70b4 |
X509_set_default_verify_paths(ssl_ctx->cert);
|
|
Packit |
8f70b4 |
#else
|
|
Packit |
8f70b4 |
SSLeay_add_ssl_algorithms();
|
|
Packit |
8f70b4 |
ssl_ctx=SSL_CTX_new(SSLv23_client_method());
|
|
Packit |
8f70b4 |
long options=SSL_OP_ALL|SSL_OP_NO_TICKET|SSL_OP_NO_SSLv2;
|
|
Packit |
8f70b4 |
const char *priority=ResMgr::Query("ssl:priority", 0);
|
|
Packit |
8f70b4 |
if(priority && *priority)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
static const struct ssl_option {
|
|
Packit |
8f70b4 |
const char name[8];
|
|
Packit |
8f70b4 |
long option;
|
|
Packit |
8f70b4 |
} opt_table[]={
|
|
Packit |
8f70b4 |
{"-SSL3.0",SSL_OP_NO_SSLv3},
|
|
Packit |
8f70b4 |
{"-TLS1.0",SSL_OP_NO_TLSv1},
|
|
Packit |
8f70b4 |
{"-TLS1.1",SSL_OP_NO_TLSv1_1},
|
|
Packit |
8f70b4 |
{"-TLS1.2",SSL_OP_NO_TLSv1_2},
|
|
Packit |
8f70b4 |
{"",0}
|
|
Packit |
8f70b4 |
};
|
|
Packit |
8f70b4 |
char *to_parse=alloca_strdup(priority);
|
|
Packit |
8f70b4 |
for(char *ptr=strtok(to_parse,":"); ptr; ptr=strtok(NULL,":")) {
|
|
Packit |
8f70b4 |
if(*ptr && !strncmp(ptr+1,"VERS-",5)) {
|
|
Packit |
8f70b4 |
ptr[5]=ptr[0];
|
|
Packit |
8f70b4 |
ptr+=5;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
for(const ssl_option *opt=opt_table; opt->name[0]; opt++) {
|
|
Packit |
8f70b4 |
if(!strcmp(ptr,opt->name)) {
|
|
Packit |
8f70b4 |
options|=opt->option;
|
|
Packit |
8f70b4 |
Log::global->Format(9,"ssl: applied %s option\n",ptr);
|
|
Packit |
8f70b4 |
break;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
SSL_CTX_set_options(ssl_ctx, options);
|
|
Packit |
8f70b4 |
SSL_CTX_set_cipher_list(ssl_ctx, "ALL:!aNULL:!eNULL:!SSLv2:!LOW:!EXP:!MD5:@STRENGTH");
|
|
Packit |
8f70b4 |
SSL_CTX_set_verify(ssl_ctx,SSL_VERIFY_PEER,lftp_ssl_openssl::verify_callback);
|
|
Packit |
8f70b4 |
// SSL_CTX_set_default_passwd_cb(ssl_ctx,lftp_ssl_passwd_callback);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
const char *ca_file=ResMgr::Query("ssl:ca-file",0);
|
|
Packit |
8f70b4 |
const char *ca_path=ResMgr::Query("ssl:ca-path",0);
|
|
Packit |
8f70b4 |
if(ca_file && !*ca_file)
|
|
Packit |
8f70b4 |
ca_file=0;
|
|
Packit |
8f70b4 |
if(ca_path && !*ca_path)
|
|
Packit |
8f70b4 |
ca_path=0;
|
|
Packit |
8f70b4 |
if(ca_file || ca_path)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(!SSL_CTX_load_verify_locations(ssl_ctx,ca_file,ca_path))
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
fprintf(stderr,"WARNING: SSL_CTX_load_verify_locations(%s,%s) failed\n",
|
|
Packit |
8f70b4 |
ca_file?ca_file:"NULL",
|
|
Packit |
8f70b4 |
ca_path?ca_path:"NULL");
|
|
Packit |
8f70b4 |
SSL_CTX_set_default_verify_paths(ssl_ctx);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
else
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
SSL_CTX_set_default_verify_paths(ssl_ctx);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
const char *crl_file=ResMgr::Query("ssl:crl-file",0);
|
|
Packit |
8f70b4 |
const char *crl_path=ResMgr::Query("ssl:crl-path",0);
|
|
Packit |
8f70b4 |
if(crl_file && !*crl_file)
|
|
Packit |
8f70b4 |
crl_file=0;
|
|
Packit |
8f70b4 |
if(crl_path && !*crl_path)
|
|
Packit |
8f70b4 |
crl_path=0;
|
|
Packit |
8f70b4 |
if(crl_file || crl_path)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
crl_store=X509_STORE_new();
|
|
Packit |
8f70b4 |
if(!X509_STORE_load_locations(crl_store,crl_file,crl_path))
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
fprintf(stderr,"WARNING: X509_STORE_load_locations(%s,%s) failed\n",
|
|
Packit |
8f70b4 |
crl_file?crl_file:"NULL",
|
|
Packit |
8f70b4 |
crl_path?crl_path:"NULL");
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
#endif /* SSLEAY_VERSION_NUMBER < 0x0800 */
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
lftp_ssl_openssl_instance::~lftp_ssl_openssl_instance()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
SSL_CTX_free(ssl_ctx);
|
|
Packit |
8f70b4 |
X509_STORE_free(crl_store);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
lftp_ssl_openssl::lftp_ssl_openssl(int fd1,handshake_mode_t m,const char *h)
|
|
Packit |
8f70b4 |
: lftp_ssl_base(fd1,m,h)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(!instance)
|
|
Packit |
8f70b4 |
global_init();
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
ssl=SSL_new(instance->ssl_ctx);
|
|
Packit |
8f70b4 |
SSL_set_fd(ssl,fd);
|
|
Packit |
8f70b4 |
SSL_ctrl(ssl,SSL_CTRL_MODE,SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER,0);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(h && ResMgr::QueryBool("ssl:use-sni",h)) {
|
|
Packit |
8f70b4 |
if(!SSL_set_tlsext_host_name(ssl, h))
|
|
Packit |
8f70b4 |
fprintf(stderr,"WARNING: failed to configure server name indication (SNI) TLS extension\n");
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
void lftp_ssl_openssl::load_keys()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
const char *key_file =ResMgr::Query("ssl:key-file",hostname);
|
|
Packit |
8f70b4 |
const char *cert_file=ResMgr::Query("ssl:cert-file",hostname);
|
|
Packit |
8f70b4 |
if(key_file && !*key_file)
|
|
Packit |
8f70b4 |
key_file=0;
|
|
Packit |
8f70b4 |
if(cert_file && !*cert_file)
|
|
Packit |
8f70b4 |
cert_file=0;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(cert_file)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(!key_file)
|
|
Packit |
8f70b4 |
key_file=cert_file;
|
|
Packit |
8f70b4 |
if(SSL_use_certificate_file(ssl,cert_file,SSL_FILETYPE_PEM)<=0)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
// FIXME
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
if(SSL_use_PrivateKey_file(ssl,key_file,SSL_FILETYPE_PEM)<=0)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
// FIXME
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
if(!SSL_check_private_key(ssl))
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
// FIXME
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
void lftp_ssl_openssl::shutdown()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(handshake_done)
|
|
Packit |
8f70b4 |
SSL_shutdown(ssl);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
lftp_ssl_openssl::~lftp_ssl_openssl()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
SSL_free(ssl);
|
|
Packit |
8f70b4 |
ssl=0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
static lftp_ssl_openssl *verify_callback_ssl;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
bool lftp_ssl_openssl::check_fatal(int res)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
return !(SSL_get_error(ssl,res)==SSL_ERROR_SYSCALL
|
|
Packit |
8f70b4 |
&& (ERR_get_error()==0 || temporary_network_error(errno)));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
int lftp_ssl_openssl::do_handshake()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(handshake_done)
|
|
Packit |
8f70b4 |
return DONE;
|
|
Packit |
8f70b4 |
if(handshake_mode==SERVER)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
// FIXME: SSL_accept
|
|
Packit |
8f70b4 |
return RETRY;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
errno=0;
|
|
Packit |
8f70b4 |
verify_callback_ssl=this;
|
|
Packit |
8f70b4 |
int res=SSL_connect(ssl);
|
|
Packit |
8f70b4 |
verify_callback_ssl=0;
|
|
Packit |
8f70b4 |
if(res<=0)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(BIO_sock_should_retry(res))
|
|
Packit |
8f70b4 |
return RETRY;
|
|
Packit |
8f70b4 |
else if (SSL_want_x509_lookup(ssl))
|
|
Packit |
8f70b4 |
return RETRY;
|
|
Packit |
8f70b4 |
else // error
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
fatal=check_fatal(res);
|
|
Packit |
8f70b4 |
set_error("SSL_connect",strerror());
|
|
Packit |
8f70b4 |
return ERROR;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
handshake_done=true;
|
|
Packit |
8f70b4 |
check_certificate();
|
|
Packit |
8f70b4 |
SMTask::current->Timeout(0);
|
|
Packit |
8f70b4 |
return DONE;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
int lftp_ssl_openssl::read(char *buf,int size)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(error)
|
|
Packit |
8f70b4 |
return ERROR;
|
|
Packit |
8f70b4 |
int res=do_handshake();
|
|
Packit |
8f70b4 |
if(res!=DONE)
|
|
Packit |
8f70b4 |
return res;
|
|
Packit |
8f70b4 |
errno=0;
|
|
Packit |
8f70b4 |
res=SSL_read(ssl,buf,size);
|
|
Packit |
8f70b4 |
if(res<0)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(BIO_sock_should_retry(res))
|
|
Packit |
8f70b4 |
return RETRY;
|
|
Packit |
8f70b4 |
else if (SSL_want_x509_lookup(ssl))
|
|
Packit |
8f70b4 |
return RETRY;
|
|
Packit |
8f70b4 |
else // error
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
fatal=check_fatal(res);
|
|
Packit |
8f70b4 |
set_error("SSL_read",strerror());
|
|
Packit |
8f70b4 |
return ERROR;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
return res;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
int lftp_ssl_openssl::write(const char *buf,int size)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(error)
|
|
Packit |
8f70b4 |
return ERROR;
|
|
Packit |
8f70b4 |
int res=do_handshake();
|
|
Packit |
8f70b4 |
if(res!=DONE)
|
|
Packit |
8f70b4 |
return res;
|
|
Packit |
8f70b4 |
if(size==0)
|
|
Packit |
8f70b4 |
return 0;
|
|
Packit |
8f70b4 |
errno=0;
|
|
Packit |
8f70b4 |
res=SSL_write(ssl,buf,size);
|
|
Packit |
8f70b4 |
if(res<0)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
if(BIO_sock_should_retry(res))
|
|
Packit |
8f70b4 |
return RETRY;
|
|
Packit |
8f70b4 |
else if (SSL_want_x509_lookup(ssl))
|
|
Packit |
8f70b4 |
return RETRY;
|
|
Packit |
8f70b4 |
else // error
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
fatal=check_fatal(res);
|
|
Packit |
8f70b4 |
set_error("SSL_write",strerror());
|
|
Packit |
8f70b4 |
return ERROR;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
return res;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
bool lftp_ssl_openssl::want_in()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
return SSL_want_read(ssl);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
bool lftp_ssl_openssl::want_out()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
return SSL_want_write(ssl);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
void lftp_ssl_openssl::copy_sid(const lftp_ssl_openssl *o)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
SSL_copy_session_id(ssl,o->ssl);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
const char *lftp_ssl_openssl::strerror()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
SSL_load_error_strings();
|
|
Packit |
8f70b4 |
int error=ERR_get_error();
|
|
Packit |
8f70b4 |
const char *ssl_error=0;
|
|
Packit |
8f70b4 |
if(ERR_GET_LIB(error)==ERR_LIB_SSL)
|
|
Packit |
8f70b4 |
ssl_error=ERR_reason_error_string(error);
|
|
Packit |
8f70b4 |
else
|
|
Packit |
8f70b4 |
ssl_error=ERR_error_string(error,NULL);
|
|
Packit |
8f70b4 |
if(!ssl_error)
|
|
Packit |
8f70b4 |
ssl_error="error";
|
|
Packit |
8f70b4 |
return ssl_error;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* This one is (very much!) based on work by Ralf S. Engelschall <rse@engelschall.com>.
|
|
Packit |
8f70b4 |
* Comments by Ralf. */
|
|
Packit |
8f70b4 |
int lftp_ssl_openssl::verify_crl(X509_STORE_CTX *ctx)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
X509_OBJECT *obj=0;
|
|
Packit |
8f70b4 |
X509_NAME *subject=0;
|
|
Packit |
8f70b4 |
X509_NAME *issuer=0;
|
|
Packit |
8f70b4 |
X509 *xs=0;
|
|
Packit |
8f70b4 |
X509_CRL *crl=0;
|
|
Packit |
8f70b4 |
X509_REVOKED *revoked=0;
|
|
Packit |
8f70b4 |
X509_STORE_CTX *store_ctx=0;
|
|
Packit |
8f70b4 |
long serial;
|
|
Packit |
8f70b4 |
int i, n, rc;
|
|
Packit |
8f70b4 |
char *cp=0;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/*
|
|
Packit |
8f70b4 |
* Unless a revocation store for CRLs was created we
|
|
Packit |
8f70b4 |
* cannot do any CRL-based verification, of course.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
if (!instance->crl_store)
|
|
Packit |
8f70b4 |
return 1;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/*
|
|
Packit |
8f70b4 |
* Determine certificate ingredients in advance
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
xs = X509_STORE_CTX_get_current_cert(ctx);
|
|
Packit |
8f70b4 |
subject = X509_get_subject_name(xs);
|
|
Packit |
8f70b4 |
issuer = X509_get_issuer_name(xs);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/*
|
|
Packit |
8f70b4 |
* OpenSSL provides the general mechanism to deal with CRLs but does not
|
|
Packit |
8f70b4 |
* use them automatically when verifying certificates, so we do it
|
|
Packit |
8f70b4 |
* explicitly here. We will check the CRL for the currently checked
|
|
Packit |
8f70b4 |
* certificate, if there is such a CRL in the store.
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* We come through this procedure for each certificate in the certificate
|
|
Packit |
8f70b4 |
* chain, starting with the root-CA's certificate. At each step we've to
|
|
Packit |
8f70b4 |
* both verify the signature on the CRL (to make sure it's a valid CRL)
|
|
Packit |
8f70b4 |
* and it's revocation list (to make sure the current certificate isn't
|
|
Packit |
8f70b4 |
* revoked). But because to check the signature on the CRL we need the
|
|
Packit |
8f70b4 |
* public key of the issuing CA certificate (which was already processed
|
|
Packit |
8f70b4 |
* one round before), we've a little problem. But we can both solve it and
|
|
Packit |
8f70b4 |
* at the same time optimize the processing by using the following
|
|
Packit |
8f70b4 |
* verification scheme (idea and code snippets borrowed from the GLOBUS
|
|
Packit |
8f70b4 |
* project):
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* 1. We'll check the signature of a CRL in each step when we find a CRL
|
|
Packit |
8f70b4 |
* through the _subject_ name of the current certificate. This CRL
|
|
Packit |
8f70b4 |
* itself will be needed the first time in the next round, of course.
|
|
Packit |
8f70b4 |
* But we do the signature processing one round before this where the
|
|
Packit |
8f70b4 |
* public key of the CA is available.
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* 2. We'll check the revocation list of a CRL in each step when
|
|
Packit |
8f70b4 |
* we find a CRL through the _issuer_ name of the current certificate.
|
|
Packit |
8f70b4 |
* This CRLs signature was then already verified one round before.
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* This verification scheme allows a CA to revoke its own certificate as
|
|
Packit |
8f70b4 |
* well, of course.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/*
|
|
Packit |
8f70b4 |
* Try to retrieve a CRL corresponding to the _subject_ of
|
|
Packit |
8f70b4 |
* the current certificate in order to verify it's integrity.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
obj = X509_OBJECT_new();
|
|
Packit |
8f70b4 |
store_ctx = X509_STORE_CTX_new();
|
|
Packit |
8f70b4 |
X509_STORE_CTX_init(store_ctx, instance->crl_store, NULL, NULL);
|
|
Packit |
8f70b4 |
rc = X509_STORE_get_by_subject(store_ctx, X509_LU_CRL, subject, obj);
|
|
Packit |
8f70b4 |
X509_STORE_CTX_free(store_ctx); store_ctx=0;
|
|
Packit |
8f70b4 |
crl = X509_OBJECT_get0_X509_CRL(obj);
|
|
Packit |
8f70b4 |
if (rc > 0 && crl != NULL) {
|
|
Packit |
8f70b4 |
/*
|
|
Packit |
8f70b4 |
* Verify the signature on this CRL
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
if (X509_CRL_verify(crl, X509_get_pubkey(xs)) <= 0) {
|
|
Packit |
8f70b4 |
Log::global->Format(0,"Invalid signature on CRL!\n");
|
|
Packit |
8f70b4 |
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE);
|
|
Packit |
8f70b4 |
X509_OBJECT_free(obj); obj=0;
|
|
Packit |
8f70b4 |
return 0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/*
|
|
Packit |
8f70b4 |
* Check date of CRL to make sure it's not expired
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
i = X509_cmp_current_time(X509_CRL_get0_nextUpdate(crl));
|
|
Packit |
8f70b4 |
if (i == 0) {
|
|
Packit |
8f70b4 |
Log::global->Format(0,"Found CRL has invalid nextUpdate field.\n");
|
|
Packit |
8f70b4 |
X509_STORE_CTX_set_error(ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
|
|
Packit |
8f70b4 |
X509_OBJECT_free(obj); obj=0;
|
|
Packit |
8f70b4 |
return 0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
if (i < 0) {
|
|
Packit |
8f70b4 |
Log::global->Format(0,"Found CRL is expired - revoking all certificates until you get updated CRL.\n");
|
|
Packit |
8f70b4 |
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_HAS_EXPIRED);
|
|
Packit |
8f70b4 |
X509_OBJECT_free(obj); obj=0;
|
|
Packit |
8f70b4 |
return 0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
X509_OBJECT_free(obj); obj=0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/*
|
|
Packit |
8f70b4 |
* Try to retrieve a CRL corresponding to the _issuer_ of
|
|
Packit |
8f70b4 |
* the current certificate in order to check for revocation.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
obj = X509_OBJECT_new();
|
|
Packit |
8f70b4 |
store_ctx = X509_STORE_CTX_new();
|
|
Packit |
8f70b4 |
X509_STORE_CTX_init(store_ctx, instance->crl_store, NULL, NULL);
|
|
Packit |
8f70b4 |
rc = X509_STORE_get_by_subject(store_ctx, X509_LU_CRL, issuer, obj);
|
|
Packit |
8f70b4 |
X509_STORE_CTX_free(store_ctx); store_ctx=0;
|
|
Packit |
8f70b4 |
crl = X509_OBJECT_get0_X509_CRL(obj);
|
|
Packit |
8f70b4 |
if (rc > 0 && crl != NULL) {
|
|
Packit |
8f70b4 |
/*
|
|
Packit |
8f70b4 |
* Check if the current certificate is revoked by this CRL
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
|
|
Packit |
8f70b4 |
for (i = 0; i < n; i++) {
|
|
Packit |
8f70b4 |
revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
|
|
Packit |
8f70b4 |
const ASN1_INTEGER *revoked_serial = X509_REVOKED_get0_serialNumber(revoked);
|
|
Packit |
8f70b4 |
if (ASN1_INTEGER_cmp(revoked_serial, X509_get_serialNumber(xs)) == 0) {
|
|
Packit |
8f70b4 |
serial = ASN1_INTEGER_get(revoked_serial);
|
|
Packit |
8f70b4 |
cp = X509_NAME_oneline(issuer, NULL, 0);
|
|
Packit |
8f70b4 |
Log::global->Format(0,
|
|
Packit |
8f70b4 |
"Certificate with serial %ld (0x%lX) revoked per CRL from issuer %s\n",
|
|
Packit |
8f70b4 |
serial, serial, cp ? cp : "(ERROR)");
|
|
Packit |
8f70b4 |
free(cp); cp=0;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED);
|
|
Packit |
8f70b4 |
X509_OBJECT_free(obj); obj=0;
|
|
Packit |
8f70b4 |
return 0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
X509_OBJECT_free(obj); obj=0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
return 1;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
static bool convert_from_utf8(char *str,int len)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
DirectedBuffer translate(DirectedBuffer::GET);
|
|
Packit |
8f70b4 |
translate.SetTranslation("UTF-8",false);
|
|
Packit |
8f70b4 |
translate.PutTranslated(str,len);
|
|
Packit |
8f70b4 |
const char *str1,*str2;
|
|
Packit |
8f70b4 |
int len1,len2;
|
|
Packit |
8f70b4 |
translate.Get(&str1,&len1);
|
|
Packit |
8f70b4 |
if(len1>len)
|
|
Packit |
8f70b4 |
return false; // no room to store expanded string
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
// be safe and try to convert back to UTF-8
|
|
Packit |
8f70b4 |
DirectedBuffer translate_back(DirectedBuffer::PUT);
|
|
Packit |
8f70b4 |
translate_back.SetTranslation("UTF-8",false);
|
|
Packit |
8f70b4 |
translate_back.PutTranslated(str1,len1);
|
|
Packit |
8f70b4 |
translate_back.Get(&str2,&len2);
|
|
Packit |
8f70b4 |
if(len2!=len || memcmp(str2,str,len))
|
|
Packit |
8f70b4 |
return false; // conversion error
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
memcpy(str,str1,len1);
|
|
Packit |
8f70b4 |
str[len1]=0;
|
|
Packit |
8f70b4 |
return true;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* begin curl code */
|
|
Packit |
8f70b4 |
/* Copyright (c) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se> */
|
|
Packit |
8f70b4 |
#define Curl_raw_toupper c_toupper
|
|
Packit |
8f70b4 |
#define Curl_raw_equal !strcmp
|
|
Packit |
8f70b4 |
#define Curl_raw_nequal !strncmp
|
|
Packit |
8f70b4 |
#define Curl_inet_pton inet_pton
|
|
Packit |
8f70b4 |
#if INET6
|
|
Packit |
8f70b4 |
# define ENABLE_IPV6 1
|
|
Packit |
8f70b4 |
#endif
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/*
|
|
Packit |
8f70b4 |
* Match a hostname against a wildcard pattern.
|
|
Packit |
8f70b4 |
* E.g.
|
|
Packit |
8f70b4 |
* "foo.host.com" matches "*.host.com".
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* We use the matching rule described in RFC6125, section 6.4.3.
|
|
Packit |
8f70b4 |
* http://tools.ietf.org/html/rfc6125#section-6.4.3
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* In addition: ignore trailing dots in the host names and wildcards, so that
|
|
Packit |
8f70b4 |
* the names are used normalized. This is what the browsers do.
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* Do not allow wildcard matching on IP numbers. There are apparently
|
|
Packit |
8f70b4 |
* certificates being used with an IP address in the CN field, thus making no
|
|
Packit |
8f70b4 |
* apparent distinction between a name and an IP. We need to detect the use of
|
|
Packit |
8f70b4 |
* an IP address and not wildcard match on such names.
|
|
Packit |
8f70b4 |
*
|
|
Packit |
8f70b4 |
* NOTE: hostmatch() gets called with copied buffers so that it can modify the
|
|
Packit |
8f70b4 |
* contents at will.
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
#define CURL_HOST_NOMATCH 0
|
|
Packit |
8f70b4 |
#define CURL_HOST_MATCH 1
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
static int hostmatch(char *hostname, char *pattern)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
const char *pattern_label_end, *pattern_wildcard, *hostname_label_end;
|
|
Packit |
8f70b4 |
int wildcard_enabled;
|
|
Packit |
8f70b4 |
size_t prefixlen, suffixlen;
|
|
Packit |
8f70b4 |
struct in_addr ignored;
|
|
Packit |
8f70b4 |
#ifdef ENABLE_IPV6
|
|
Packit |
8f70b4 |
struct sockaddr_in6 si6;
|
|
Packit |
8f70b4 |
#endif
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* normalize pattern and hostname by stripping off trailing dots */
|
|
Packit |
8f70b4 |
size_t len = strlen(hostname);
|
|
Packit |
8f70b4 |
if(hostname[len-1]=='.')
|
|
Packit |
8f70b4 |
hostname[len-1]=0;
|
|
Packit |
8f70b4 |
len = strlen(pattern);
|
|
Packit |
8f70b4 |
if(pattern[len-1]=='.')
|
|
Packit |
8f70b4 |
pattern[len-1]=0;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
pattern_wildcard = strchr(pattern, '*');
|
|
Packit |
8f70b4 |
if(pattern_wildcard == NULL)
|
|
Packit |
8f70b4 |
return Curl_raw_equal(pattern, hostname) ?
|
|
Packit |
8f70b4 |
CURL_HOST_MATCH : CURL_HOST_NOMATCH;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* detect IP address as hostname and fail the match if so */
|
|
Packit |
8f70b4 |
if(Curl_inet_pton(AF_INET, hostname, &ignored) > 0)
|
|
Packit |
8f70b4 |
return CURL_HOST_NOMATCH;
|
|
Packit |
8f70b4 |
#ifdef ENABLE_IPV6
|
|
Packit |
8f70b4 |
else if(Curl_inet_pton(AF_INET6, hostname, &si6.sin6_addr) > 0)
|
|
Packit |
8f70b4 |
return CURL_HOST_NOMATCH;
|
|
Packit |
8f70b4 |
#endif
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* We require at least 2 dots in pattern to avoid too wide wildcard
|
|
Packit |
8f70b4 |
match. */
|
|
Packit |
8f70b4 |
wildcard_enabled = 1;
|
|
Packit |
8f70b4 |
pattern_label_end = strchr(pattern, '.');
|
|
Packit |
8f70b4 |
if(pattern_label_end == NULL || strchr(pattern_label_end+1, '.') == NULL ||
|
|
Packit |
8f70b4 |
pattern_wildcard > pattern_label_end ||
|
|
Packit |
8f70b4 |
Curl_raw_nequal(pattern, "xn--", 4)) {
|
|
Packit |
8f70b4 |
wildcard_enabled = 0;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
if(!wildcard_enabled)
|
|
Packit |
8f70b4 |
return Curl_raw_equal(pattern, hostname) ?
|
|
Packit |
8f70b4 |
CURL_HOST_MATCH : CURL_HOST_NOMATCH;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
hostname_label_end = strchr(hostname, '.');
|
|
Packit |
8f70b4 |
if(hostname_label_end == NULL ||
|
|
Packit |
8f70b4 |
!Curl_raw_equal(pattern_label_end, hostname_label_end))
|
|
Packit |
8f70b4 |
return CURL_HOST_NOMATCH;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* The wildcard must match at least one character, so the left-most
|
|
Packit |
8f70b4 |
label of the hostname is at least as large as the left-most label
|
|
Packit |
8f70b4 |
of the pattern. */
|
|
Packit |
8f70b4 |
if(hostname_label_end - hostname < pattern_label_end - pattern)
|
|
Packit |
8f70b4 |
return CURL_HOST_NOMATCH;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
prefixlen = pattern_wildcard - pattern;
|
|
Packit |
8f70b4 |
suffixlen = pattern_label_end - (pattern_wildcard+1);
|
|
Packit |
8f70b4 |
return Curl_raw_nequal(pattern, hostname, prefixlen) &&
|
|
Packit |
8f70b4 |
Curl_raw_nequal(pattern_wildcard+1, hostname_label_end - suffixlen,
|
|
Packit |
8f70b4 |
suffixlen) ?
|
|
Packit |
8f70b4 |
CURL_HOST_MATCH : CURL_HOST_NOMATCH;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
static int cert_hostcheck(const char *match_pattern, const char *hostname)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
char *matchp;
|
|
Packit |
8f70b4 |
char *hostp;
|
|
Packit |
8f70b4 |
int res = 0;
|
|
Packit |
8f70b4 |
if(!match_pattern || !*match_pattern ||
|
|
Packit |
8f70b4 |
!hostname || !*hostname) /* sanity check */
|
|
Packit |
8f70b4 |
;
|
|
Packit |
8f70b4 |
else {
|
|
Packit |
8f70b4 |
matchp = strdup(match_pattern);
|
|
Packit |
8f70b4 |
if(matchp) {
|
|
Packit |
8f70b4 |
hostp = strdup(hostname);
|
|
Packit |
8f70b4 |
if(hostp) {
|
|
Packit |
8f70b4 |
if(hostmatch(hostp, matchp) == CURL_HOST_MATCH)
|
|
Packit |
8f70b4 |
res= 1;
|
|
Packit |
8f70b4 |
free(hostp);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
free(matchp);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
return res;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* Quote from RFC2818 section 3.1 "Server Identity"
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
If a subjectAltName extension of type dNSName is present, that MUST
|
|
Packit |
8f70b4 |
be used as the identity. Otherwise, the (most specific) Common Name
|
|
Packit |
8f70b4 |
field in the Subject field of the certificate MUST be used. Although
|
|
Packit |
8f70b4 |
the use of the Common Name is existing practice, it is deprecated and
|
|
Packit |
8f70b4 |
Certification Authorities are encouraged to use the dNSName instead.
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
Matching is performed using the matching rules specified by
|
|
Packit |
8f70b4 |
[RFC2459]. If more than one identity of a given type is present in
|
|
Packit |
8f70b4 |
the certificate (e.g., more than one dNSName name, a match in any one
|
|
Packit |
8f70b4 |
of the set is considered acceptable.) Names may contain the wildcard
|
|
Packit |
8f70b4 |
character * which is considered to match any single domain name
|
|
Packit |
8f70b4 |
component or component fragment. E.g., *.a.com matches foo.a.com but
|
|
Packit |
8f70b4 |
not bar.foo.a.com. f*.com matches foo.com but not bar.com.
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
In some cases, the URI is specified as an IP address rather than a
|
|
Packit |
8f70b4 |
hostname. In this case, the iPAddress subjectAltName must be present
|
|
Packit |
8f70b4 |
in the certificate and must exactly match the IP in the URI.
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
void lftp_ssl_openssl::check_certificate()
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
X509 *server_cert = SSL_get_peer_certificate (ssl);
|
|
Packit |
8f70b4 |
if (!server_cert)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
set_cert_error(xstring::format(_("No certificate presented by %s.\n"),
|
|
Packit |
8f70b4 |
quotearg_style (escape_quoting_style, hostname)),xstring::null);
|
|
Packit |
8f70b4 |
return;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
bool check_hostname = ResMgr::QueryBool("ssl:check-hostname", hostname);
|
|
Packit |
8f70b4 |
if(!check_hostname) {
|
|
Packit |
8f70b4 |
Log::global->Format(0, "WARNING: Certificate verification: hostname checking disabled\n");
|
|
Packit |
8f70b4 |
return;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
int matched = -1; /* -1 is no alternative match yet, 1 means match and 0
|
|
Packit |
8f70b4 |
means mismatch */
|
|
Packit |
8f70b4 |
int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */
|
|
Packit |
8f70b4 |
size_t addrlen = 0;
|
|
Packit |
8f70b4 |
STACK_OF(GENERAL_NAME) *altnames;
|
|
Packit |
8f70b4 |
#ifdef ENABLE_IPV6
|
|
Packit |
8f70b4 |
struct in6_addr addr;
|
|
Packit |
8f70b4 |
#else
|
|
Packit |
8f70b4 |
struct in_addr addr;
|
|
Packit |
8f70b4 |
#endif
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
sockaddr_u fd_addr;
|
|
Packit |
8f70b4 |
socklen_t fd_addr_len = sizeof(fd_addr);
|
|
Packit |
8f70b4 |
getsockname(fd,&fd_addr.sa,&fd_addr_len);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#ifdef ENABLE_IPV6
|
|
Packit |
8f70b4 |
if(fd_addr.sa.sa_family==AF_INET6 &&
|
|
Packit |
8f70b4 |
Curl_inet_pton(AF_INET6, hostname, &addr)) {
|
|
Packit |
8f70b4 |
target = GEN_IPADD;
|
|
Packit |
8f70b4 |
addrlen = sizeof(struct in6_addr);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
else
|
|
Packit |
8f70b4 |
#endif
|
|
Packit |
8f70b4 |
if(Curl_inet_pton(AF_INET, hostname, &addr)) {
|
|
Packit |
8f70b4 |
target = GEN_IPADD;
|
|
Packit |
8f70b4 |
addrlen = sizeof(struct in_addr);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* get a "list" of alternative names */
|
|
Packit |
8f70b4 |
altnames = (STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(altnames) {
|
|
Packit |
8f70b4 |
int numalts;
|
|
Packit |
8f70b4 |
int i;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* get amount of alternatives, RFC2459 claims there MUST be at least
|
|
Packit |
8f70b4 |
one, but we don't depend on it... */
|
|
Packit |
8f70b4 |
numalts = sk_GENERAL_NAME_num(altnames);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* loop through all alternatives while none has matched */
|
|
Packit |
8f70b4 |
for (i=0; (i
|
|
Packit |
8f70b4 |
/* get a handle to alternative name number i */
|
|
Packit |
8f70b4 |
const GENERAL_NAME *check = sk_GENERAL_NAME_value(altnames, i);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* only check alternatives of the same type the target is */
|
|
Packit |
8f70b4 |
if(check->type == target) {
|
|
Packit |
8f70b4 |
/* get data and length */
|
|
Packit |
8f70b4 |
const char *altptr = (char *)ASN1_STRING_get0_data(check->d.ia5);
|
|
Packit |
8f70b4 |
size_t altlen = (size_t) ASN1_STRING_length(check->d.ia5);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
switch(target) {
|
|
Packit |
8f70b4 |
case GEN_DNS: /* name/pattern comparison */
|
|
Packit |
8f70b4 |
/* The OpenSSL man page explicitly says: "In general it cannot be
|
|
Packit |
8f70b4 |
assumed that the data returned by ASN1_STRING_get0_data() is null
|
|
Packit |
8f70b4 |
terminated or does not contain embedded nulls." But also that
|
|
Packit |
8f70b4 |
"The actual format of the data will depend on the actual string
|
|
Packit |
8f70b4 |
type itself: for example for and IA5String the data will be ASCII"
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
Gisle researched the OpenSSL sources:
|
|
Packit |
8f70b4 |
"I checked the 0.9.6 and 0.9.8 sources before my patch and
|
|
Packit |
8f70b4 |
it always 0-terminates an IA5String."
|
|
Packit |
8f70b4 |
*/
|
|
Packit |
8f70b4 |
if((altlen == strlen(altptr)) &&
|
|
Packit |
8f70b4 |
/* if this isn't true, there was an embedded zero in the name
|
|
Packit |
8f70b4 |
string and we cannot match it. */
|
|
Packit |
8f70b4 |
cert_hostcheck(altptr, hostname))
|
|
Packit |
8f70b4 |
matched = 1;
|
|
Packit |
8f70b4 |
else
|
|
Packit |
8f70b4 |
matched = 0;
|
|
Packit |
8f70b4 |
break;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
case GEN_IPADD: /* IP address comparison */
|
|
Packit |
8f70b4 |
/* compare alternative IP address if the data chunk is the same size
|
|
Packit |
8f70b4 |
our server IP address is */
|
|
Packit |
8f70b4 |
if((altlen == addrlen) && !memcmp(altptr, &addr, altlen))
|
|
Packit |
8f70b4 |
matched = 1;
|
|
Packit |
8f70b4 |
else
|
|
Packit |
8f70b4 |
matched = 0;
|
|
Packit |
8f70b4 |
break;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
GENERAL_NAMES_free(altnames);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(matched == 1)
|
|
Packit |
8f70b4 |
/* an alternative name matched the server hostname */
|
|
Packit |
8f70b4 |
Log::global->Format(9, "Certificate verification: subjectAltName: %s matched\n", quote(hostname));
|
|
Packit |
8f70b4 |
else if(matched == 0) {
|
|
Packit |
8f70b4 |
/* an alternative name field existed, but didn't match and then
|
|
Packit |
8f70b4 |
we MUST fail */
|
|
Packit |
8f70b4 |
set_cert_error(xstring::format("subjectAltName does not match %s", quote(hostname)),get_fp(server_cert));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
else {
|
|
Packit |
8f70b4 |
/* we have to look to the last occurence of a commonName in the
|
|
Packit |
8f70b4 |
distinguished one to get the most significant one. */
|
|
Packit |
8f70b4 |
int j,i=-1 ;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* The following is done because of a bug in 0.9.6b */
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
unsigned char *nulstr = (unsigned char *)"";
|
|
Packit |
8f70b4 |
unsigned char *peer_CN = nulstr;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
X509_NAME *name = X509_get_subject_name(server_cert) ;
|
|
Packit |
8f70b4 |
if(name)
|
|
Packit |
8f70b4 |
while((j = X509_NAME_get_index_by_NID(name, NID_commonName, i))>=0)
|
|
Packit |
8f70b4 |
i=j;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* we have the name entry and we will now convert this to a string
|
|
Packit |
8f70b4 |
that we can use for comparison. Doing this we support BMPstring,
|
|
Packit |
8f70b4 |
UTF8 etc. */
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(i>=0) {
|
|
Packit |
8f70b4 |
ASN1_STRING *tmp = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name,i));
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
/* In OpenSSL 0.9.7d and earlier, ASN1_STRING_to_UTF8 fails if the input
|
|
Packit |
8f70b4 |
is already UTF-8 encoded. We check for this case and copy the raw
|
|
Packit |
8f70b4 |
string manually to avoid the problem. This code can be made
|
|
Packit |
8f70b4 |
conditional in the future when OpenSSL has been fixed. Work-around
|
|
Packit |
8f70b4 |
brought by Alexis S. L. Carvalho. */
|
|
Packit |
8f70b4 |
if(tmp) {
|
|
Packit |
8f70b4 |
if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) {
|
|
Packit |
8f70b4 |
j = ASN1_STRING_length(tmp);
|
|
Packit |
8f70b4 |
if(j >= 0) {
|
|
Packit |
8f70b4 |
peer_CN = (unsigned char*)OPENSSL_malloc(j+1);
|
|
Packit |
8f70b4 |
if(peer_CN) {
|
|
Packit |
8f70b4 |
memcpy(peer_CN, ASN1_STRING_get0_data(tmp), j);
|
|
Packit |
8f70b4 |
peer_CN[j] = '\0';
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
else /* not a UTF8 name */
|
|
Packit |
8f70b4 |
j = ASN1_STRING_to_UTF8(&peer_CN, tmp);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(peer_CN && ((int)strlen((char *)peer_CN) != j)) {
|
|
Packit |
8f70b4 |
/* there was a terminating zero before the end of string, this
|
|
Packit |
8f70b4 |
cannot match and we return failure! */
|
|
Packit |
8f70b4 |
set_cert_error("illegal cert name field (contains NUL character)",get_fp(server_cert));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(peer_CN == nulstr)
|
|
Packit |
8f70b4 |
peer_CN = NULL;
|
|
Packit |
8f70b4 |
else {
|
|
Packit |
8f70b4 |
/* convert peer_CN from UTF8 */
|
|
Packit |
8f70b4 |
if(!convert_from_utf8((char*)peer_CN, strlen((char*)peer_CN)))
|
|
Packit |
8f70b4 |
set_cert_error("invalid cert name field (cannot convert from UTF8)",get_fp(server_cert));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(cert_error)
|
|
Packit |
8f70b4 |
/* error already detected, pass through */
|
|
Packit |
8f70b4 |
;
|
|
Packit |
8f70b4 |
else if(!peer_CN) {
|
|
Packit |
8f70b4 |
set_cert_error("unable to obtain common name from peer certificate",get_fp(server_cert));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
else if(!cert_hostcheck((const char *)peer_CN, hostname)) {
|
|
Packit |
8f70b4 |
set_cert_error(xstring::format("certificate subject name %s does not match "
|
|
Packit |
8f70b4 |
"target host name %s", quote_n(0,(const char *)peer_CN), quote_n(1,hostname)),get_fp(server_cert));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
else {
|
|
Packit |
8f70b4 |
Log::global->Format(9, "Certificate verification: common name: %s matched\n", quote((char*)peer_CN));
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
if(peer_CN)
|
|
Packit |
8f70b4 |
OPENSSL_free(peer_CN);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
/* end curl code */
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#include <sha1.h>
|
|
Packit |
8f70b4 |
const xstring& lftp_ssl_openssl::get_fp(X509 *cert)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
static xstring fp;
|
|
Packit |
8f70b4 |
fp.truncate();
|
|
Packit |
8f70b4 |
unsigned fp_len=SHA1_DIGEST_SIZE;
|
|
Packit |
8f70b4 |
if(!X509_digest(cert, EVP_sha1(), (unsigned char*)fp.add_space(fp_len), &fp_len))
|
|
Packit |
8f70b4 |
return xstring::null;
|
|
Packit |
8f70b4 |
fp.add_commit(fp_len);
|
|
Packit |
8f70b4 |
return fp;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
int lftp_ssl_openssl::verify_callback(int ok,X509_STORE_CTX *ctx)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
static X509 *prev_cert=0;
|
|
Packit |
8f70b4 |
X509 *cert=X509_STORE_CTX_get_current_cert(ctx);
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(cert!=prev_cert)
|
|
Packit |
8f70b4 |
{
|
|
Packit |
8f70b4 |
int depth = X509_STORE_CTX_get_error_depth(ctx);
|
|
Packit |
8f70b4 |
X509_NAME *subject = X509_get_subject_name(cert);
|
|
Packit |
8f70b4 |
X509_NAME *issuer = X509_get_issuer_name(cert);
|
|
Packit |
8f70b4 |
char *subject_line = X509_NAME_oneline(subject, NULL, 0);
|
|
Packit |
8f70b4 |
char *issuer_line = X509_NAME_oneline(issuer, NULL, 0);
|
|
Packit |
8f70b4 |
Log::global->Format(3,"Certificate depth: %d; subject: %s; issuer: %s\n",
|
|
Packit |
8f70b4 |
depth,subject_line,issuer_line);
|
|
Packit |
8f70b4 |
free(subject_line);
|
|
Packit |
8f70b4 |
free(issuer_line);
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
if(ok && !verify_crl(ctx))
|
|
Packit |
8f70b4 |
ok=0;
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
int error=X509_STORE_CTX_get_error(ctx);
|
|
Packit |
8f70b4 |
if(!ok)
|
|
Packit |
8f70b4 |
verify_callback_ssl->set_cert_error(X509_verify_cert_error_string(error),get_fp(cert));
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
prev_cert=cert;
|
|
Packit |
8f70b4 |
return 1;
|
|
Packit |
8f70b4 |
}
|
|
Packit |
8f70b4 |
#endif // USE_OPENSSL
|
|
Packit |
8f70b4 |
|
|
Packit |
8f70b4 |
#endif // USE_SSL
|