|
/***************************************************************************
|
|
$RCSfile$
|
|
-------------------
|
|
cvs : $Id$
|
|
begin : Wed May 05 2004
|
|
copyright : (C) 2004 by Martin Preuss
|
|
email : martin@libchipcard.de
|
|
|
|
***************************************************************************
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Lesser General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2.1 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library 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 *
|
|
* Lesser General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Lesser General Public *
|
|
* License along with this library; 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
|
|
|
|
/* #define DEBUG_SSL_LOG */
|
|
|
|
#include "nettransportssl_p.h"
|
|
#include <gwenhywfar/misc.h>
|
|
#include <gwenhywfar/text.h>
|
|
#include <gwenhywfar/debug.h>
|
|
#include <gwenhywfar/buffer.h>
|
|
#include <gwenhywfar/directory.h>
|
|
#include "inetsocket_l.h"
|
|
#include <gwenhywfar/waitcallback.h>
|
|
|
|
#include <openssl/pem.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/dh.h>
|
|
#include <openssl/rand.h>
|
|
|
|
#include <openssl/conf.h>
|
|
#include <openssl/x509v3.h>
|
|
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#ifdef HAVE_SYS_SOCKET_H
|
|
# include <sys/socket.h>
|
|
#endif /* HAVE_SYS_SOCKET_H */
|
|
|
|
#ifdef ENABLE_NLS
|
|
# include <libintl.h>
|
|
# include <locale.h>
|
|
# define I18N(text) dgettext("gwenhywfar", text)
|
|
#else
|
|
# define I18N(text) text
|
|
#endif
|
|
|
|
|
|
GWEN_INHERIT(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL);
|
|
|
|
|
|
static GWEN_NETTRANSPORTSSL_GETPASSWD_FN
|
|
gwen_netransportssl_getPasswordFn=0;
|
|
|
|
static GWEN_NETTRANSPORTSSL_ASKADDCERT_FN
|
|
gwen_netransportssl_askAddCertFn=0;
|
|
|
|
|
|
static GWEN_NETTRANSPORTSSL_ASKADDCERT_FN2
|
|
gwen_netransportssl_askAddCertFn2=0;
|
|
|
|
static void *gwen_netransportssl_askAddCertUserData=0;
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
GWEN_NETTRANSPORTSSL *GWEN_NetTransportSSLData_new(){
|
|
GWEN_NETTRANSPORTSSL *skd;
|
|
|
|
GWEN_NEW_OBJECT(GWEN_NETTRANSPORTSSL, skd);
|
|
DBG_MEM_INC("GWEN_NETTRANSPORTSSL", 0);
|
|
return skd;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
void GWEN_NetTransportSSLData_free(GWEN_NETTRANSPORTSSL *skd){
|
|
if (skd) {
|
|
DBG_MEM_DEC("GWEN_NETTRANSPORTSSL");
|
|
if (skd->ownSocket)
|
|
GWEN_Socket_free(skd->socket);
|
|
free(skd->CAdir);
|
|
free(skd->newCAdir);
|
|
free(skd->ownCertFile);
|
|
free(skd->dhfile);
|
|
free(skd->cipherList);
|
|
free(skd->peerCertificate);
|
|
free(skd);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
GWEN_NETTRANSPORT *GWEN_NetTransportSSL_new(GWEN_SOCKET *sk,
|
|
const char *capath,
|
|
const char *newcapath,
|
|
const char *ownCertFile,
|
|
const char *dhfile,
|
|
int secure,
|
|
int relinquish){
|
|
GWEN_NETTRANSPORT *tr;
|
|
GWEN_NETTRANSPORTSSL *skd;
|
|
|
|
tr=GWEN_NetTransport_new();
|
|
skd=GWEN_NetTransportSSLData_new();
|
|
GWEN_INHERIT_SETDATA(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL,
|
|
tr, skd, GWEN_NetTransportSSL_FreeData);
|
|
|
|
skd->socket=sk;
|
|
skd->ownSocket=relinquish;
|
|
if (capath)
|
|
skd->CAdir=strdup(capath);
|
|
if (newcapath)
|
|
skd->newCAdir=strdup(newcapath);
|
|
if (ownCertFile)
|
|
skd->ownCertFile=strdup(ownCertFile);
|
|
if (dhfile)
|
|
skd->dhfile=strdup(dhfile);
|
|
|
|
skd->secure=secure;
|
|
|
|
GWEN_NetTransport_SetStartConnectFn(tr,
|
|
GWEN_NetTransportSSL_StartConnect);
|
|
GWEN_NetTransport_SetStartAcceptFn(tr,
|
|
GWEN_NetTransportSSL_StartAccept);
|
|
GWEN_NetTransport_SetStartDisconnectFn(tr,
|
|
GWEN_NetTransportSSL_StartDisconnect);
|
|
GWEN_NetTransport_SetReadFn(tr,
|
|
GWEN_NetTransportSSL_Read);
|
|
GWEN_NetTransport_SetWriteFn(tr,
|
|
GWEN_NetTransportSSL_Write);
|
|
GWEN_NetTransport_SetAddSocketsFn(tr,
|
|
GWEN_NetTransportSSL_AddSockets);
|
|
GWEN_NetTransport_SetWorkFn(tr,
|
|
GWEN_NetTransportSSL_Work);
|
|
|
|
return tr;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
void GWEN_NetTransportSSL_FreeData(void *bp, void *p){
|
|
GWEN_NetTransportSSLData_free((GWEN_NETTRANSPORTSSL*)p);
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
GWEN_DB_NODE*
|
|
GWEN_NetTransportSSL_GetPeerCertificate(const GWEN_NETTRANSPORT *tr) {
|
|
GWEN_NETTRANSPORTSSL *skd;
|
|
|
|
assert(tr);
|
|
skd=GWEN_INHERIT_GETDATA(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL, tr);
|
|
assert(skd);
|
|
|
|
return skd->peerCertificate;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
int GWEN_NetTransportSSL_IsOfType(GWEN_NETTRANSPORT *tr) {
|
|
return GWEN_INHERIT_ISOFTYPE(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL, tr);
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
int GWEN_NetTransportSSL_IsSecure(const GWEN_NETTRANSPORT *tr){
|
|
GWEN_NETTRANSPORTSSL *skd;
|
|
|
|
assert(tr);
|
|
skd=GWEN_INHERIT_GETDATA(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL, tr);
|
|
assert(skd);
|
|
|
|
return skd->isSecure;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
GWEN_NETTRANSPORT_RESULT
|
|
GWEN_NetTransportSSL_StartConnect(GWEN_NETTRANSPORT *tr){
|
|
GWEN_NETTRANSPORTSSL *skd;
|
|
GWEN_ERRORCODE err;
|
|
char addrBuffer[128];
|
|
|
|
assert(tr);
|
|
skd=GWEN_INHERIT_GETDATA(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL, tr);
|
|
assert(skd);
|
|
|
|
GWEN_InetAddr_GetAddress(GWEN_NetTransport_GetPeerAddr(tr),
|
|
addrBuffer, sizeof(addrBuffer));
|
|
|
|
if (GWEN_NetTransport_GetStatus(tr)!=GWEN_NetTransportStatusUnconnected &&
|
|
GWEN_NetTransport_GetStatus(tr)!=GWEN_NetTransportStatusPDisconnected){
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Socket is not unconnected (status \"%s\")",
|
|
GWEN_NetTransport_StatusName(GWEN_NetTransport_GetStatus(tr)));
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Starting to connect to %s (port %d)",
|
|
addrBuffer,
|
|
GWEN_InetAddr_GetPort(GWEN_NetTransport_GetPeerAddr(tr)));
|
|
|
|
/* arm socket code */
|
|
err=GWEN_Socket_Open(skd->socket);
|
|
if (!GWEN_Error_IsOk(err)) {
|
|
DBG_ERROR_ERR(GWEN_LOGDOMAIN, err);
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
|
|
/* set nonblocking */
|
|
err=GWEN_Socket_SetBlocking(skd->socket, 0);
|
|
if (!GWEN_Error_IsOk(err)) {
|
|
DBG_ERROR_ERR(GWEN_LOGDOMAIN, err);
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
|
|
/* actually start to connect */
|
|
skd->active=1;
|
|
err=GWEN_Socket_Connect(skd->socket, GWEN_NetTransport_GetPeerAddr(tr));
|
|
/* not yet finished or real error ? */
|
|
if (!GWEN_Error_IsOk(err)) {
|
|
if (GWEN_Error_GetType(err)!=GWEN_Error_FindType(GWEN_SOCKET_ERROR_TYPE) ||
|
|
GWEN_Error_GetCode(err)!=GWEN_SOCKET_ERROR_IN_PROGRESS) {
|
|
/* real error, so return that error */
|
|
DBG_ERROR_ERR(GWEN_LOGDOMAIN, err);
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
|
|
/* adjust status (physically connecting) */
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusPConnecting);
|
|
}
|
|
else {
|
|
/* connection succeeded */
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Connection established with %s (port %d)",
|
|
addrBuffer,
|
|
GWEN_InetAddr_GetPort(GWEN_NetTransport_GetPeerAddr(tr)));
|
|
/* adjust status (logically connected) */
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusPConnected);
|
|
}
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
return GWEN_NetTransportResultOk;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
GWEN_NETTRANSPORT_RESULT
|
|
GWEN_NetTransportSSL_StartAccept(GWEN_NETTRANSPORT *tr){
|
|
GWEN_NETTRANSPORTSSL *skd;
|
|
GWEN_ERRORCODE err;
|
|
char addrBuffer[128];
|
|
|
|
assert(tr);
|
|
skd=GWEN_INHERIT_GETDATA(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL, tr);
|
|
if (GWEN_NetTransport_GetStatus(tr)!=GWEN_NetTransportStatusUnconnected) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Socket is not unconnected (%d)",
|
|
GWEN_NetTransport_GetStatus(tr));
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
|
|
GWEN_InetAddr_GetAddress(GWEN_NetTransport_GetLocalAddr(tr),
|
|
addrBuffer, sizeof(addrBuffer));
|
|
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Starting to listen on %s (port %d)",
|
|
addrBuffer,
|
|
GWEN_InetAddr_GetPort(GWEN_NetTransport_GetLocalAddr(tr)));
|
|
|
|
/* arm socket code */
|
|
err=GWEN_Socket_Open(skd->socket);
|
|
if (!GWEN_Error_IsOk(err)) {
|
|
DBG_ERROR_ERR(GWEN_LOGDOMAIN, err);
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
|
|
/* set nonblocking */
|
|
err=GWEN_Socket_SetBlocking(skd->socket, 0);
|
|
if (!GWEN_Error_IsOk(err)) {
|
|
DBG_ERROR_ERR(GWEN_LOGDOMAIN, err);
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
|
|
/* reuse address */
|
|
err=GWEN_Socket_SetReuseAddress(skd->socket, 1);
|
|
if (!GWEN_Error_IsOk(err)) {
|
|
DBG_ERROR_ERR(GWEN_LOGDOMAIN, err);
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
|
|
/* bind socket to local address */
|
|
skd->active=0;
|
|
err=GWEN_Socket_Bind(skd->socket, GWEN_NetTransport_GetLocalAddr(tr));
|
|
if (!GWEN_Error_IsOk(err)) {
|
|
DBG_ERROR_ERR(GWEN_LOGDOMAIN, err);
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
|
|
/* start listening */
|
|
err=GWEN_Socket_Listen(skd->socket, 10);
|
|
if (!GWEN_Error_IsOk(err)) {
|
|
DBG_ERROR_ERR(GWEN_LOGDOMAIN, err);
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
|
|
/* adjust status (listening) */
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusListening);
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
|
|
return GWEN_NetTransportResultOk;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
GWEN_NETTRANSPORT_RESULT
|
|
GWEN_NetTransportSSL_StartDisconnect(GWEN_NETTRANSPORT *tr){
|
|
GWEN_NETTRANSPORTSSL *skd;
|
|
GWEN_NETTRANSPORT_STATUS st;
|
|
int rv;
|
|
|
|
assert(tr);
|
|
skd=GWEN_INHERIT_GETDATA(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL, tr);
|
|
|
|
/* check status */
|
|
st=GWEN_NetTransport_GetStatus(tr);
|
|
if (st==GWEN_NetTransportStatusUnconnected ||
|
|
st==GWEN_NetTransportStatusPDisconnected ||
|
|
st==GWEN_NetTransportStatusDisabled) {
|
|
DBG_INFO(GWEN_LOGDOMAIN,
|
|
"Socket is inactive: %s (%d)",
|
|
GWEN_NetTransport_StatusName(GWEN_NetTransport_GetStatus(tr)),
|
|
GWEN_NetTransport_GetStatus(tr));
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
|
|
if (!skd->ssl) {
|
|
/* connection closed */
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Connection closed");
|
|
GWEN_Socket_Close(skd->socket);
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusPDisconnected);
|
|
return GWEN_NetTransportResultOk;
|
|
}
|
|
rv=SSL_shutdown(skd->ssl);
|
|
if (!rv) {
|
|
/* send a TCP_FIN to trigger the other side's close_notify */
|
|
shutdown(GWEN_Socket_GetSocketInt(skd->socket), 1);
|
|
rv=SSL_shutdown(skd->ssl);
|
|
}
|
|
|
|
if (rv==1 || rv==-1) {
|
|
/* connection closed */
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Connection closed");
|
|
GWEN_Socket_Close(skd->socket);
|
|
SSL_free(skd->ssl);
|
|
skd->ssl=0;
|
|
SSL_CTX_free(skd->ssl_ctx);
|
|
skd->ssl_ctx=0;
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusPDisconnected);
|
|
return GWEN_NetTransportResultOk;
|
|
}
|
|
|
|
/* adjust status (LDisconnecting) */
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusLDisconnecting);
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
|
|
return GWEN_NetTransportResultOk;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
GWEN_NETTRANSPORT_RESULT
|
|
GWEN_NetTransportSSL_Read(GWEN_NETTRANSPORT *tr,
|
|
char *buffer,
|
|
int *bsize){
|
|
GWEN_NETTRANSPORTSSL *skd;
|
|
int rv;
|
|
|
|
assert(tr);
|
|
skd=GWEN_INHERIT_GETDATA(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL, tr);
|
|
|
|
/* check status */
|
|
if (GWEN_NetTransport_GetStatus(tr)!=GWEN_NetTransportStatusLConnected) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Socket is not connected (%d)",
|
|
GWEN_NetTransport_GetStatus(tr));
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
|
|
/* try to read */
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Reading up to %d bytes while status \"%s\"",
|
|
*bsize,
|
|
SSL_state_string_long(skd->ssl));
|
|
|
|
ERR_clear_error();
|
|
rv=SSL_read(skd->ssl, buffer, *bsize);
|
|
if (rv<1) {
|
|
int sslerr;
|
|
|
|
sslerr=SSL_get_error(skd->ssl, rv);
|
|
if (sslerr==SSL_ERROR_WANT_READ)
|
|
return GWEN_NetTransportResultWantRead;
|
|
else if (sslerr==SSL_ERROR_WANT_WRITE)
|
|
return GWEN_NetTransportResultWantWrite;
|
|
else {
|
|
if (sslerr==SSL_ERROR_SYSCALL && errno==0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Connection just went down (%d: %s)",
|
|
sslerr,
|
|
GWEN_NetTransportSSL_ErrorString(sslerr));
|
|
}
|
|
else {
|
|
if (sslerr==SSL_ERROR_ZERO_RETURN) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Connection closed");
|
|
}
|
|
else {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "List of pending SSL errors:");
|
|
ERR_print_errors_fp(stderr); /* DEBUG */
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "SSL error: %s (%d)",
|
|
GWEN_NetTransportSSL_ErrorString(sslerr),
|
|
sslerr);
|
|
}
|
|
}
|
|
|
|
GWEN_Socket_Close(skd->socket);
|
|
SSL_free(skd->ssl);
|
|
skd->ssl=0;
|
|
SSL_CTX_free(skd->ssl_ctx);
|
|
skd->ssl_ctx=0;
|
|
|
|
if (sslerr==SSL_ERROR_ZERO_RETURN) {
|
|
/* connection closed, no real error */
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusPDisconnected);
|
|
*bsize=0;
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
return GWEN_NetTransportResultOk;
|
|
}
|
|
else {
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusDisabled);
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
}
|
|
}
|
|
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Read %d bytes:", rv);
|
|
GWEN_Text_LogString(buffer, rv, 0, GWEN_LoggerLevelVerbous);
|
|
*bsize=rv;
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
return GWEN_NetTransportResultOk;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
GWEN_NETTRANSPORT_RESULT
|
|
GWEN_NetTransportSSL_Write(GWEN_NETTRANSPORT *tr,
|
|
const char *buffer,
|
|
int *bsize){
|
|
GWEN_NETTRANSPORTSSL *skd;
|
|
int rv;
|
|
|
|
assert(tr);
|
|
skd=GWEN_INHERIT_GETDATA(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL, tr);
|
|
|
|
/* check status */
|
|
if (GWEN_NetTransport_GetStatus(tr)!=GWEN_NetTransportStatusLConnected) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Socket is not connected (%d)",
|
|
GWEN_NetTransport_GetStatus(tr));
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
|
|
/* try to read */
|
|
ERR_clear_error();
|
|
rv=SSL_write(skd->ssl, buffer, *bsize);
|
|
if (rv<1) {
|
|
int sslerr;
|
|
|
|
sslerr=SSL_get_error(skd->ssl, rv);
|
|
if (sslerr==SSL_ERROR_WANT_READ)
|
|
return GWEN_NetTransportResultWantRead;
|
|
else if (sslerr==SSL_ERROR_WANT_WRITE)
|
|
return GWEN_NetTransportResultWantWrite;
|
|
else {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "SSL error: %s (%d)",
|
|
GWEN_NetTransportSSL_ErrorString(sslerr),
|
|
sslerr);
|
|
GWEN_Socket_Close(skd->socket);
|
|
SSL_free(skd->ssl);
|
|
skd->ssl=0;
|
|
SSL_CTX_free(skd->ssl_ctx);
|
|
skd->ssl_ctx=0;
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusUnconnected);
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
}
|
|
else if (rv==0) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Broken pipe");
|
|
GWEN_Socket_Close(skd->socket);
|
|
SSL_free(skd->ssl);
|
|
skd->ssl=0;
|
|
SSL_CTX_free(skd->ssl_ctx);
|
|
skd->ssl_ctx=0;
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusUnconnected);
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
return GWEN_NetTransportResultError;
|
|
}
|
|
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Written %d bytes:", rv);
|
|
GWEN_Text_LogString(buffer, rv, 0, GWEN_LoggerLevelVerbous);
|
|
#ifdef DEBUG_SSL_LOG
|
|
if (1) {
|
|
FILE *f;
|
|
|
|
DBG_NOTICE(GWEN_LOGDOMAIN, "Saving...");
|
|
f=fopen("/tmp/written.bin", "a+");
|
|
if (!f) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "fopen: %s", strerror(errno));
|
|
}
|
|
else {
|
|
if (fwrite(buffer, rv, 1, f)!=1) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "fwrite: %s", strerror(errno));
|
|
}
|
|
if (fclose(f)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "fclose: %s", strerror(errno));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
*bsize=rv;
|
|
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
return GWEN_NetTransportResultOk;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
int GWEN_NetTransportSSL_AddSockets(GWEN_NETTRANSPORT *tr,
|
|
GWEN_SOCKETSET *sset,
|
|
int forReading){
|
|
GWEN_NETTRANSPORTSSL *skd;
|
|
GWEN_ERRORCODE err;
|
|
|
|
assert(tr);
|
|
skd=GWEN_INHERIT_GETDATA(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL, tr);
|
|
|
|
/* add socket */
|
|
err=GWEN_SocketSet_AddSocket(sset, skd->socket);
|
|
if (!GWEN_Error_IsOk(err)) {
|
|
DBG_INFO_ERR(GWEN_LOGDOMAIN, err);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
int GWEN_NetTransportSSL__Check_Cert(GWEN_NETTRANSPORTSSL *skd,
|
|
const char *name){
|
|
X509 *peer;
|
|
char cn[256];
|
|
|
|
if (SSL_get_verify_result(skd->ssl)!=X509_V_OK) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Invalid certificate");
|
|
return -1;
|
|
}
|
|
/* check common name */
|
|
peer=SSL_get_peer_certificate(skd->ssl);
|
|
X509_NAME_get_text_by_NID(X509_get_subject_name(peer),
|
|
NID_commonName, cn, sizeof(cn));
|
|
if(strcasecmp(cn, name)) {
|
|
DBG_WARN(GWEN_LOGDOMAIN, "Common name does not match (\"%s\" != \"%s\")",
|
|
cn, name);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
int GWEN_NetTransportSSL_GetPassword(GWEN_NETTRANSPORT *tr,
|
|
char *buffer, int num, int rwflag){
|
|
if (gwen_netransportssl_getPasswordFn)
|
|
return gwen_netransportssl_getPasswordFn(tr, buffer, num, rwflag);
|
|
else {
|
|
DBG_WARN(GWEN_LOGDOMAIN, "No getPasswordFn set");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
void
|
|
GWEN_NetTransportSSL_SetGetPasswordFn(GWEN_NETTRANSPORTSSL_GETPASSWD_FN fn){
|
|
gwen_netransportssl_getPasswordFn=fn;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
GWEN_NETTRANSPORTSSL_GETPASSWD_FN
|
|
GWEN_NetTransportSSL_GetGetPasswordFn(){
|
|
return gwen_netransportssl_getPasswordFn;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
int GWEN_NetTransportSSL_PasswordCB(char *buffer, int num,
|
|
int rwflag, void *userdata){
|
|
return GWEN_NetTransportSSL_GetPassword((GWEN_NETTRANSPORT*)userdata,
|
|
buffer, num, rwflag);
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
GWEN_NETTRANSPORTSSL_ASKADDCERT_RESULT
|
|
GWEN_NetTransportSSL__AskAddCert(GWEN_NETTRANSPORT *tr,
|
|
GWEN_DB_NODE *cert){
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Would ask user about this:");
|
|
if (GWEN_Logger_GetLevel(GWEN_LOGDOMAIN)>=GWEN_LoggerLevelInfo)
|
|
GWEN_DB_Dump(cert, stderr, 2);
|
|
|
|
if (gwen_netransportssl_askAddCertFn2)
|
|
return gwen_netransportssl_askAddCertFn2(tr,
|
|
cert,
|
|
gwen_netransportssl_askAddCertUserData);
|
|
else if (gwen_netransportssl_askAddCertFn)
|
|
return gwen_netransportssl_askAddCertFn(tr, cert);
|
|
else {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "No askAddCert function set");
|
|
return GWEN_NetTransportSSL_AskAddCertResultNo;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
void
|
|
GWEN_NetTransportSSL_SetAskAddCertFn(GWEN_NETTRANSPORTSSL_ASKADDCERT_FN fn){
|
|
gwen_netransportssl_askAddCertFn=fn;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
GWEN_NETTRANSPORTSSL_ASKADDCERT_FN GWEN_NetTransportSSL_GetAskAddCertFn(){
|
|
return gwen_netransportssl_askAddCertFn;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
void
|
|
GWEN_NetTransportSSL_SetAskAddCertFn2(GWEN_NETTRANSPORTSSL_ASKADDCERT_FN2 fn,
|
|
void *user_data){
|
|
gwen_netransportssl_askAddCertFn2=fn;
|
|
gwen_netransportssl_askAddCertUserData=user_data;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
void GWEN_NetTransportSSL__InfoCallBack(SSL *s, int where, int ret){
|
|
const char *str;
|
|
int w;
|
|
|
|
w=where& ~SSL_ST_MASK;
|
|
if (w & SSL_ST_CONNECT)
|
|
str="SSL_connect";
|
|
else if (w & SSL_ST_ACCEPT)
|
|
str="SSL_accept";
|
|
else
|
|
str="undefined";
|
|
|
|
if (where & SSL_CB_LOOP){
|
|
DBG_INFO(GWEN_LOGDOMAIN,"%s: %s",str,SSL_state_string_long(s));
|
|
}
|
|
else if (where & SSL_CB_ALERT){
|
|
str=(where & SSL_CB_READ)?"read":"write";
|
|
DBG_INFO(GWEN_LOGDOMAIN, "SSL3 alert %s: %s: %s",
|
|
str,
|
|
SSL_alert_type_string_long(ret),
|
|
SSL_alert_desc_string_long(ret));
|
|
}
|
|
else if (where & SSL_CB_EXIT){
|
|
if (ret==0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "%s: failed in \"%s\"",
|
|
str,
|
|
SSL_state_string_long(s));
|
|
}
|
|
else if (ret<0){
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "%s: error in \"%s\"",
|
|
str,
|
|
SSL_state_string_long(s));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
int GWEN_NetTransportSSL__SaveCert(GWEN_NETTRANSPORT *tr,
|
|
X509 *cert,
|
|
const char *dir,
|
|
int overwrite) {
|
|
FILE *f;
|
|
const char *fmode = "";
|
|
char cn[256];
|
|
GWEN_NETTRANSPORTSSL *skd;
|
|
X509_NAME *nm;
|
|
|
|
assert(tr);
|
|
skd=GWEN_INHERIT_GETDATA(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL, tr);
|
|
assert(skd);
|
|
|
|
nm=X509_get_subject_name(cert);
|
|
X509_NAME_get_text_by_NID(nm,
|
|
NID_commonName, cn, sizeof(cn));
|
|
|
|
if (!dir)
|
|
dir=skd->CAdir;
|
|
/*if (skd->CAdir) { */
|
|
if (dir) {
|
|
GWEN_BUFFER *nbuf;
|
|
unsigned long hash;
|
|
char numbuf[32];
|
|
int i;
|
|
GWEN_TYPE_UINT32 pos;
|
|
|
|
hash=X509_NAME_hash(nm);
|
|
snprintf(numbuf, sizeof(numbuf), "%08lx", hash);
|
|
nbuf=GWEN_Buffer_new(0, 128, 0, 1);
|
|
GWEN_Buffer_AppendString(nbuf, dir);
|
|
/* check path, create it if necessary */
|
|
if (GWEN_Directory_GetPath(GWEN_Buffer_GetStart(nbuf),
|
|
GWEN_PATH_FLAGS_CHECKROOT)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Invalid path (\"%s\")", GWEN_Buffer_GetStart(nbuf));
|
|
GWEN_Buffer_free(nbuf);
|
|
return -1;
|
|
}
|
|
GWEN_Buffer_AppendByte(nbuf, '/');
|
|
GWEN_Buffer_AppendString(nbuf, numbuf);
|
|
pos=GWEN_Buffer_GetPos(nbuf);
|
|
for (i=0; i<GWEN_NETTRANSPORTSSL_MAXCOLL; i++) {
|
|
snprintf(numbuf, sizeof(numbuf), "%d", i);
|
|
GWEN_Buffer_Crop(nbuf, 0, pos);
|
|
GWEN_Buffer_SetPos(nbuf, pos);
|
|
GWEN_Buffer_AppendByte(nbuf, '.');
|
|
GWEN_Buffer_AppendString(nbuf, numbuf);
|
|
if (overwrite)
|
|
/* overwrite older incoming certificates */
|
|
break;
|
|
if (GWEN_Directory_GetPath(GWEN_Buffer_GetStart(nbuf),
|
|
GWEN_PATH_FLAGS_NAMEMUSTEXIST|
|
|
GWEN_PATH_FLAGS_VARIABLE|
|
|
GWEN_PATH_FLAGS_CHECKROOT)) {
|
|
break;
|
|
}
|
|
}
|
|
if (i>=GWEN_NETTRANSPORTSSL_MAXCOLL) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Maximum number of hash collisions reached!");
|
|
GWEN_Buffer_free(nbuf);
|
|
return -1;
|
|
}
|
|
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Saving file as \"%s\"", GWEN_Buffer_GetStart(nbuf));
|
|
f=fopen(GWEN_Buffer_GetStart(nbuf), "w+");
|
|
if (!f) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "fopen(\"%s\", \"%s\"): %s",
|
|
GWEN_Buffer_GetStart(nbuf), fmode, strerror(errno));
|
|
GWEN_Buffer_free(nbuf);
|
|
return -1;
|
|
}
|
|
GWEN_Buffer_free(nbuf);
|
|
}
|
|
else {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Don't know where to save the file...");
|
|
return -1;
|
|
}
|
|
|
|
if (!PEM_write_X509(f, cert)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Could not save certificate of \"%s\"", cn);
|
|
return 0;
|
|
}
|
|
|
|
if (fclose(f)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "fclose: %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Certificate of \"%s\" added", cn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
int GWEN_NetTransportSSL__VerifyCallBack(int preverify_ok,
|
|
X509_STORE_CTX *ctx) {
|
|
int err;
|
|
|
|
err=X509_STORE_CTX_get_error(ctx);
|
|
if (!preverify_ok) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Verify error %d: \"%s\"",
|
|
err, X509_verify_cert_error_string(err));
|
|
if (err==X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY ||
|
|
err==X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
|
|
err==X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Unknown certificate, will not abort yet");
|
|
return 1;
|
|
}
|
|
}
|
|
return preverify_ok;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
int GWEN_NetTransportSSL__SetupSSL(GWEN_NETTRANSPORT *tr, int fd){
|
|
int rv = 0;
|
|
GWEN_NETTRANSPORTSSL *skd;
|
|
|
|
assert(tr);
|
|
skd=GWEN_INHERIT_GETDATA(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL, tr);
|
|
assert(skd);
|
|
|
|
/* enable all workarounds for bugs in other SSL implementation for
|
|
* maximum compatibility */
|
|
SSL_CTX_set_options(skd->ssl_ctx, SSL_OP_ALL);
|
|
|
|
/* set default password handler and data */
|
|
SSL_CTX_set_default_passwd_cb(skd->ssl_ctx,
|
|
GWEN_NetTransportSSL_PasswordCB);
|
|
SSL_CTX_set_default_passwd_cb_userdata(skd->ssl_ctx,
|
|
tr);
|
|
|
|
if (skd->ownCertFile) {
|
|
/* load own certificate file */
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Loading certificate and keys");
|
|
if (!(SSL_CTX_use_certificate_chain_file(skd->ssl_ctx,
|
|
skd->ownCertFile))){
|
|
int sslerr;
|
|
|
|
sslerr=SSL_get_error(skd->ssl, rv);
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "SSL error reading certfile: %s (%d)",
|
|
GWEN_NetTransportSSL_ErrorString(sslerr),
|
|
sslerr);
|
|
return -1;
|
|
}
|
|
/* load keys */
|
|
if(!(SSL_CTX_use_PrivateKey_file(skd->ssl_ctx,
|
|
skd->ownCertFile,
|
|
SSL_FILETYPE_PEM))) {
|
|
int sslerr;
|
|
|
|
sslerr=SSL_get_error(skd->ssl, rv);
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "SSL error reading keyfile: %s (%d)",
|
|
GWEN_NetTransportSSL_ErrorString(sslerr),
|
|
sslerr);
|
|
return -1;
|
|
}
|
|
if (!SSL_CTX_check_private_key(skd->ssl_ctx)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Private key does not match the certificate public key");
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
|
|
/* setup locations of certificates */
|
|
if (skd->CAdir) {
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Loading certificate locations");
|
|
rv=SSL_CTX_load_verify_locations(skd->ssl_ctx, 0, skd->CAdir);
|
|
if (rv==0) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "SSL: Could not load certificate location");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
//SSL_CTX_set_verify_depth(skd->ssl_ctx, 1);
|
|
|
|
/* setup server stuff */
|
|
if (!skd->active) {
|
|
if (GWEN_NetTransport_GetStatus(tr)!=GWEN_NetTransportStatusListening) {
|
|
FILE *f;
|
|
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Loading DH params");
|
|
|
|
f=fopen(skd->dhfile, "r");
|
|
if (!f) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "SSL: fopen(%s): %s", skd->dhfile, strerror(errno));
|
|
return -1;
|
|
}
|
|
else {
|
|
DH *dh_tmp;
|
|
int codes;
|
|
|
|
dh_tmp=PEM_read_DHparams(f, NULL, NULL, NULL);
|
|
fclose(f);
|
|
if (dh_tmp==0) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "SSL: Error reading DH params");
|
|
return -1;
|
|
}
|
|
|
|
/* check for usability */
|
|
if (!DH_check(dh_tmp, &codes)){
|
|
int sslerr;
|
|
|
|
sslerr=SSL_get_error(skd->ssl, rv);
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "SSL DH_check error: %s (%d)",
|
|
GWEN_NetTransportSSL_ErrorString(sslerr),
|
|
sslerr);
|
|
DH_free(dh_tmp);
|
|
return -1;
|
|
}
|
|
|
|
if (codes & DH_CHECK_P_NOT_PRIME){
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "SSL DH error: p is not prime");
|
|
DH_free(dh_tmp);
|
|
return -1;
|
|
}
|
|
if ((codes & DH_NOT_SUITABLE_GENERATOR) &&
|
|
(codes & DH_CHECK_P_NOT_SAFE_PRIME)){
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "SSL DH error : "
|
|
"neither suitable generator or safe prime");
|
|
DH_free(dh_tmp);
|
|
return -1;
|
|
}
|
|
|
|
/* DH params seem to be ok */
|
|
if (SSL_CTX_set_tmp_dh(skd->ssl_ctx, dh_tmp)<0) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "SSL: Could not set DH params");
|
|
DH_free(dh_tmp);
|
|
return -1;
|
|
}
|
|
|
|
/* always expect peer certificate */
|
|
if (skd->secure)
|
|
SSL_CTX_set_verify(skd->ssl_ctx,
|
|
SSL_VERIFY_PEER |
|
|
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
GWEN_NetTransportSSL__VerifyCallBack);
|
|
else
|
|
SSL_CTX_set_verify(skd->ssl_ctx,
|
|
SSL_VERIFY_PEER,
|
|
GWEN_NetTransportSSL__VerifyCallBack);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* active connection */
|
|
if (skd->secure)
|
|
SSL_CTX_set_verify(skd->ssl_ctx,
|
|
SSL_VERIFY_PEER,
|
|
GWEN_NetTransportSSL__VerifyCallBack);
|
|
else
|
|
SSL_CTX_set_verify(skd->ssl_ctx,
|
|
SSL_VERIFY_NONE,
|
|
GWEN_NetTransportSSL__VerifyCallBack);
|
|
}
|
|
|
|
skd->ssl=SSL_new(skd->ssl_ctx);
|
|
if (skd->active)
|
|
SSL_set_connect_state(skd->ssl);
|
|
else
|
|
SSL_set_accept_state(skd->ssl);
|
|
|
|
/* set info callback */
|
|
SSL_set_info_callback(skd->ssl,
|
|
(void (*)()) GWEN_NetTransportSSL__InfoCallBack);
|
|
|
|
/* tell SSL to use our socket */
|
|
rv=SSL_set_fd(skd->ssl, fd);
|
|
if (rv==0) {
|
|
int sslerr;
|
|
|
|
sslerr=SSL_get_error(skd->ssl, rv);
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "SSL error setting socket: %s (%d)",
|
|
GWEN_NetTransportSSL_ErrorString(sslerr),
|
|
sslerr);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
void GWEN_NetTransportSSL__CertEntries2Db(X509_NAME *nm,
|
|
GWEN_DB_NODE *dbCert,
|
|
int nid,
|
|
const char *name) {
|
|
X509_NAME_ENTRY *e;
|
|
const char *p;
|
|
int len;
|
|
int lastpos;
|
|
char *cpy;
|
|
|
|
lastpos=-1;
|
|
for (;;) {
|
|
lastpos=X509_NAME_get_index_by_NID(nm, nid, lastpos);
|
|
if (lastpos==-1)
|
|
break;
|
|
e=X509_NAME_get_entry(nm, lastpos);
|
|
assert(e);
|
|
p=e->value->data;
|
|
len=e->value->length;
|
|
if (p) {
|
|
cpy=(char*)malloc(len+1);
|
|
memmove(cpy, p, len);
|
|
cpy[len]=0;
|
|
GWEN_DB_SetCharValue(dbCert, GWEN_DB_FLAGS_DEFAULT,
|
|
name, cpy);
|
|
free(cpy);
|
|
}
|
|
} /* for */
|
|
}
|
|
|
|
|
|
|
|
int GWEN_NetTransportSSL__ASN_UTC2Db(ASN1_TIME *d,
|
|
GWEN_DB_NODE *db,
|
|
const char *name) {
|
|
if (d->data) {
|
|
const char *s;
|
|
unsigned int i;
|
|
struct tm t;
|
|
struct tm *lt;
|
|
time_t currTime;
|
|
int isUtc;
|
|
|
|
s=(const char*)(d->data);
|
|
i=strlen(s);
|
|
if (i<10) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Bad time expression (%s)", s);
|
|
return -1;
|
|
}
|
|
currTime=time(0);
|
|
isUtc=(s[i-1]=='Z');
|
|
if (isUtc)
|
|
lt=gmtime(&currTime);
|
|
else
|
|
lt=localtime(&currTime);
|
|
|
|
memmove(&t, lt, sizeof(t));
|
|
t.tm_year=((s[0]-'0')*10)+(s[1]-'0')+100;
|
|
t.tm_mon=(((s[2]-'0')*10)+(s[3]-'0'))-1;
|
|
t.tm_mday=((s[4]-'0')*10)+(s[5]-'0');
|
|
t.tm_hour=((s[6]-'0')*10)+(s[7]-'0');
|
|
t.tm_min=((s[8]-'0')*10)+(s[9]-'0');
|
|
if (i>11)
|
|
t.tm_sec=((s[10]-'0')*10)+(s[11]-'0');
|
|
else
|
|
t.tm_sec=0;
|
|
t.tm_wday=0;
|
|
t.tm_yday=0;
|
|
|
|
currTime=mktime(&t);
|
|
GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS,
|
|
name, (unsigned int)currTime);
|
|
return 0;
|
|
}
|
|
else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
GWEN_DB_NODE *GWEN_NetTransportSSL__Cert2Db(X509 *cert) {
|
|
GWEN_DB_NODE *dbCert;
|
|
X509_NAME *nm;
|
|
ASN1_TIME *d;
|
|
EVP_PKEY *pktmp;
|
|
unsigned int md_size;
|
|
unsigned char md[EVP_MAX_MD_SIZE];
|
|
|
|
nm=X509_get_subject_name(cert);
|
|
|
|
/* setup certificate db */
|
|
dbCert=GWEN_DB_Group_new("cert");
|
|
|
|
GWEN_NetTransportSSL__CertEntries2Db(nm, dbCert,
|
|
NID_commonName,
|
|
"commonName");
|
|
GWEN_NetTransportSSL__CertEntries2Db(nm, dbCert,
|
|
NID_organizationName,
|
|
"organizationName");
|
|
GWEN_NetTransportSSL__CertEntries2Db(nm, dbCert,
|
|
NID_organizationalUnitName,
|
|
"organizationalUnitName");
|
|
GWEN_NetTransportSSL__CertEntries2Db(nm, dbCert,
|
|
NID_countryName,
|
|
"countryName");
|
|
GWEN_NetTransportSSL__CertEntries2Db(nm, dbCert,
|
|
NID_localityName,
|
|
"localityName");
|
|
GWEN_NetTransportSSL__CertEntries2Db(nm, dbCert,
|
|
NID_stateOrProvinceName,
|
|
"stateOrProvinceName");
|
|
|
|
d=X509_get_notBefore(cert);
|
|
if (d) {
|
|
if (GWEN_NetTransportSSL__ASN_UTC2Db(d, dbCert, "notBefore")) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Error in notBefore date");
|
|
}
|
|
}
|
|
|
|
d=X509_get_notAfter(cert);
|
|
if (d) {
|
|
if (GWEN_NetTransportSSL__ASN_UTC2Db(d, dbCert, "notAfter")) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Error in notBefore date");
|
|
}
|
|
}
|
|
|
|
pktmp=X509_get_pubkey(cert);
|
|
if (pktmp) {
|
|
RSA *kd;
|
|
|
|
kd=EVP_PKEY_get1_RSA(pktmp);
|
|
if (kd) {
|
|
char buffer[256];
|
|
int l;
|
|
GWEN_DB_NODE *dbKey;
|
|
GWEN_DB_NODE *dbKeyData;
|
|
|
|
dbKey=GWEN_DB_GetGroup(dbCert, GWEN_DB_FLAGS_OVERWRITE_GROUPS,
|
|
"pubKey");
|
|
|
|
assert(dbKey);
|
|
dbKeyData=GWEN_DB_GetGroup(dbKey, GWEN_DB_FLAGS_OVERWRITE_GROUPS,
|
|
"data");
|
|
GWEN_DB_SetCharValue(dbKey,
|
|
GWEN_DB_FLAGS_OVERWRITE_VARS,
|
|
"type", "RSA");
|
|
|
|
GWEN_DB_SetIntValue(dbKeyData,
|
|
GWEN_DB_FLAGS_DEFAULT |
|
|
GWEN_DB_FLAGS_OVERWRITE_VARS,
|
|
"public", 1);
|
|
if (kd->n) {
|
|
l=BN_bn2bin(kd->n, (unsigned char*) &buffer);
|
|
GWEN_DB_SetBinValue(dbKeyData,
|
|
GWEN_DB_FLAGS_DEFAULT |
|
|
GWEN_DB_FLAGS_OVERWRITE_VARS,
|
|
"n", buffer, l);
|
|
}
|
|
|
|
if (kd->e) {
|
|
l=BN_bn2bin(kd->e, (unsigned char*) &buffer);
|
|
GWEN_DB_SetBinValue(dbKeyData,
|
|
GWEN_DB_FLAGS_DEFAULT |
|
|
GWEN_DB_FLAGS_OVERWRITE_VARS,
|
|
"e", buffer, l);
|
|
}
|
|
RSA_free(kd);
|
|
} /* if there is data for the public key */
|
|
EVP_PKEY_free(pktmp);
|
|
} /* if there is a pubkey */
|
|
|
|
if (!X509_digest(cert, EVP_md5(), md, &md_size)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN,
|
|
"Error building fingerprint of the certificate");
|
|
}
|
|
if (!md_size) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN,
|
|
"Empty fingerprint of the certificate");
|
|
}
|
|
else {
|
|
GWEN_BUFFER *dbuf;
|
|
|
|
GWEN_DB_SetBinValue(dbCert,
|
|
GWEN_DB_FLAGS_DEFAULT |
|
|
GWEN_DB_FLAGS_OVERWRITE_VARS,
|
|
"fingerprint", md, md_size);
|
|
|
|
dbuf=GWEN_Buffer_new(0, 256, 0, 1);
|
|
if (GWEN_Text_ToHexBuffer(md, md_size, dbuf, 2, ':', 0)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN,
|
|
"Could not convert fingerprint to hex");
|
|
}
|
|
else {
|
|
GWEN_DB_SetCharValue(dbCert,
|
|
GWEN_DB_FLAGS_DEFAULT |
|
|
GWEN_DB_FLAGS_OVERWRITE_VARS,
|
|
"HexFingerprint", GWEN_Buffer_GetStart(dbuf));
|
|
}
|
|
GWEN_Buffer_free(dbuf);
|
|
}
|
|
|
|
return dbCert;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
GWEN_NETTRANSPORT_WORKRESULT
|
|
GWEN_NetTransportSSL_Work(GWEN_NETTRANSPORT *tr) {
|
|
GWEN_NETTRANSPORTSSL *skd;
|
|
GWEN_NETTRANSPORT_STATUS st;
|
|
GWEN_ERRORCODE err;
|
|
int rv;
|
|
|
|
assert(tr);
|
|
skd=GWEN_INHERIT_GETDATA(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL, tr);
|
|
|
|
st=GWEN_NetTransport_GetStatus(tr);
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Working with status \"%s\" (%d)",
|
|
GWEN_NetTransport_StatusName(st),
|
|
st);
|
|
switch(st) {
|
|
case GWEN_NetTransportStatusPConnecting: {
|
|
char addrBuffer[128];
|
|
DBG_VERBOUS(GWEN_LOGDOMAIN, "Still connecting");
|
|
|
|
/* get socket error to check whether the connect succeeded */
|
|
err=GWEN_Socket_GetSocketError(skd->socket);
|
|
if (!GWEN_Error_IsOk(err)) {
|
|
if (GWEN_Error_GetType(err)!=
|
|
GWEN_Error_FindType(GWEN_SOCKET_ERROR_TYPE) ||
|
|
(GWEN_Error_GetCode(err)!=GWEN_SOCKET_ERROR_TIMEOUT &&
|
|
GWEN_Error_GetCode(err)!=GWEN_SOCKET_ERROR_INTERRUPTED)) {
|
|
DBG_ERROR_ERR(GWEN_LOGDOMAIN, err);
|
|
return GWEN_NetTransportWorkResult_Error;
|
|
}
|
|
DBG_VERBOUS(GWEN_LOGDOMAIN, "Still not connected");
|
|
return GWEN_NetTransportWorkResult_NoChange;
|
|
}
|
|
/* log address */
|
|
GWEN_InetAddr_GetAddress(GWEN_NetTransport_GetPeerAddr(tr),
|
|
addrBuffer, sizeof(addrBuffer));
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Connection established with %s (port %d)",
|
|
addrBuffer,
|
|
GWEN_InetAddr_GetPort(GWEN_NetTransport_GetPeerAddr(tr)));
|
|
/* set to "physically connected" */
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusPConnected);
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Physical connection established");
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
return GWEN_NetTransportWorkResult_Change;
|
|
}
|
|
|
|
case GWEN_NetTransportStatusPConnected: {
|
|
/* establish SSL */
|
|
int fd;
|
|
|
|
/* reset security */
|
|
GWEN_DB_Group_free(skd->peerCertificate);
|
|
skd->peerCertificate=0;
|
|
skd->isSecure=0;
|
|
|
|
/* get socket handle (I know, it's ugly, but the function below is
|
|
* not exported to the outside) */
|
|
fd=GWEN_Socket_GetSocketInt(skd->socket);
|
|
if (fd==-1) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "No socket handle, cannot use this socket with SSL");
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusDisabled);
|
|
return GWEN_NetTransportWorkResult_Error;
|
|
}
|
|
|
|
/* socket ready now setup SSL */
|
|
if (skd->active)
|
|
skd->ssl_ctx=SSL_CTX_new(SSLv23_client_method());
|
|
else
|
|
skd->ssl_ctx=SSL_CTX_new(SSLv23_server_method());
|
|
|
|
if (GWEN_NetTransportSSL__SetupSSL(tr, fd)) {
|
|
GWEN_Socket_Close(skd->socket);
|
|
SSL_free(skd->ssl);
|
|
skd->ssl=0;
|
|
SSL_CTX_free(skd->ssl_ctx);
|
|
skd->ssl_ctx=0;
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusDisabled);
|
|
return GWEN_NetTransportWorkResult_Error;
|
|
}
|
|
|
|
/* set to "logically connecting" */
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusLConnecting);
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
return GWEN_NetTransportWorkResult_Change;
|
|
}
|
|
|
|
case GWEN_NetTransportStatusLConnecting: {
|
|
X509 *cert;
|
|
|
|
/* check for established SSL */
|
|
ERR_clear_error();
|
|
if (skd->active) {
|
|
DBG_VERBOUS(GWEN_LOGDOMAIN, "Calling connect");
|
|
rv=SSL_connect(skd->ssl);
|
|
}
|
|
else {
|
|
DBG_VERBOUS(GWEN_LOGDOMAIN, "Calling accept");
|
|
rv=SSL_accept(skd->ssl);
|
|
}
|
|
|
|
if (rv!=1) {
|
|
int sslerr;
|
|
|
|
sslerr=SSL_get_error(skd->ssl, rv);
|
|
if (sslerr!=SSL_ERROR_WANT_READ &&
|
|
sslerr!=SSL_ERROR_WANT_WRITE) {
|
|
if (sslerr==SSL_ERROR_SYSCALL && errno==0) {
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "SSL: Syscall error flagged, but errno is 0...");
|
|
}
|
|
else {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "SSL error: %s (%d)",
|
|
GWEN_NetTransportSSL_ErrorString(sslerr),
|
|
sslerr);
|
|
ERR_print_errors_fp(stderr);
|
|
}
|
|
|
|
GWEN_Socket_Close(skd->socket);
|
|
SSL_free(skd->ssl);
|
|
skd->ssl=0;
|
|
SSL_CTX_free(skd->ssl_ctx);
|
|
skd->ssl_ctx=0;
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusDisabled);
|
|
return GWEN_NetTransportWorkResult_Error;
|
|
}
|
|
else {
|
|
return GWEN_NetTransportWorkResult_NoChange;
|
|
}
|
|
}
|
|
|
|
/* now logically connected */
|
|
GWEN_DB_Group_free(skd->peerCertificate);
|
|
skd->peerCertificate=0;
|
|
|
|
/* show info about used cipher */
|
|
if (GWEN_Logger_GetLevel(0)>=GWEN_LoggerLevelNotice) {
|
|
SSL_CIPHER *ci;
|
|
char buffer[256];
|
|
const char *p;
|
|
|
|
ci=SSL_get_current_cipher(skd->ssl);
|
|
assert(ci);
|
|
|
|
p=SSL_CIPHER_description(ci, buffer, sizeof(buffer));
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Connected using \"%s\"", p);
|
|
}
|
|
|
|
cert=SSL_get_peer_certificate(skd->ssl);
|
|
if (!cert) {
|
|
if (skd->secure) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Peer did not send a certificate, abort");
|
|
GWEN_Socket_Close(skd->socket);
|
|
SSL_free(skd->ssl);
|
|
skd->ssl=0;
|
|
SSL_CTX_free(skd->ssl_ctx);
|
|
skd->ssl_ctx=0;
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusDisabled);
|
|
return GWEN_NetTransportWorkResult_Error;
|
|
}
|
|
else {
|
|
DBG_WARN(GWEN_LOGDOMAIN, "Peer did not send a certificate");
|
|
}
|
|
}
|
|
else {
|
|
char *certbuf;
|
|
long vr;
|
|
GWEN_DB_NODE *dbCert;
|
|
const char *s;
|
|
X509_NAME *nm;
|
|
unsigned long hash;
|
|
char numbuf[16];
|
|
int isNew;
|
|
int isError;
|
|
int isWarning;
|
|
GWEN_INETADDRESS *peerAddr;
|
|
GWEN_ERRORCODE err;
|
|
|
|
certbuf=X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
|
|
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Got a certificate: %s",
|
|
certbuf);
|
|
|
|
/* setup certificate */
|
|
isNew=0;
|
|
isError=0;
|
|
isWarning=0;
|
|
skd->peerCertificate=GWEN_NetTransportSSL__Cert2Db(cert);
|
|
dbCert=skd->peerCertificate;
|
|
|
|
err=GWEN_Socket_GetPeerAddr(skd->socket, &peerAddr);
|
|
if (!GWEN_Error_IsOk(err)) {
|
|
DBG_ERROR_ERR(GWEN_LOGDOMAIN, err);
|
|
}
|
|
else {
|
|
char addrBuffer[256];
|
|
|
|
err=GWEN_InetAddr_GetAddress(peerAddr, addrBuffer,
|
|
sizeof(addrBuffer)-1);
|
|
if (!GWEN_Error_IsOk(err)) {
|
|
DBG_ERROR_ERR(GWEN_LOGDOMAIN, err);
|
|
}
|
|
else {
|
|
GWEN_DB_SetCharValue(dbCert, GWEN_DB_FLAGS_DEFAULT,
|
|
"ipaddr", addrBuffer);
|
|
}
|
|
GWEN_InetAddr_free(peerAddr);
|
|
}
|
|
|
|
/* setup statusText and statusCode */
|
|
vr=SSL_get_verify_result(skd->ssl);
|
|
switch(vr) {
|
|
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
|
|
s=I18N("New certificate");
|
|
isNew=1;
|
|
break;
|
|
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
|
|
s=I18N("Unable to get issuer certificate");
|
|
isWarning=1;
|
|
break;
|
|
case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
|
|
s=I18N("Unable to decrypt cert signature");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
|
|
s=I18N("Unable to decode issuer public key");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_CERT_SIGNATURE_FAILURE:
|
|
s=I18N("Cert signature failure");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_CERT_NOT_YET_VALID:
|
|
s=I18N("Cert not yet valid");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_CERT_HAS_EXPIRED:
|
|
s=I18N("Cert has expired");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
|
|
s=I18N("Self-signed root cert");
|
|
isWarning=1;
|
|
break;
|
|
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
|
|
s=I18N("Self-signed cert");
|
|
isWarning=1;
|
|
break;
|
|
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
|
|
s=I18N("Unable to verify leaf signature");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_CERT_CHAIN_TOO_LONG:
|
|
s=I18N("Cert chain too long");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_CERT_REVOKED:
|
|
s=I18N("Cert revoked");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_INVALID_CA:
|
|
s=I18N("Invalid CA");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_CERT_UNTRUSTED:
|
|
s=I18N("Cert untrusted");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_CERT_REJECTED:
|
|
s=I18N("Cert rejected");
|
|
isError=1;
|
|
break;
|
|
|
|
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
|
|
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
|
|
case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
|
|
case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
|
|
case X509_V_ERR_PATH_LENGTH_EXCEEDED:
|
|
case X509_V_ERR_INVALID_PURPOSE:
|
|
s=I18N("Formal error in certificate");
|
|
isError=1;
|
|
break;
|
|
|
|
case X509_V_ERR_OUT_OF_MEM:
|
|
s=I18N("Out of memory");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_UNABLE_TO_GET_CRL:
|
|
s=I18N("Unable to get CRL");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
|
|
s=I18N("Unable to decrypt CRL signature");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_CRL_SIGNATURE_FAILURE:
|
|
s=I18N("CRL signature failure");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_CRL_NOT_YET_VALID:
|
|
s=I18N("CRL not yet valid");
|
|
isError=1;
|
|
break;
|
|
case X509_V_ERR_CRL_HAS_EXPIRED:
|
|
s=I18N("CRL has expired");
|
|
isError=1;
|
|
break;
|
|
case X509_V_OK:
|
|
s=I18N("Certificate ok");
|
|
break;
|
|
default:
|
|
s=I18N("Unknown SSL error");
|
|
isError=1;
|
|
}
|
|
GWEN_DB_SetCharValue(dbCert, GWEN_DB_FLAGS_DEFAULT,
|
|
"statusText", s);
|
|
nm=X509_get_subject_name(cert);
|
|
if (nm) {
|
|
hash=X509_NAME_hash(nm);
|
|
snprintf(numbuf, sizeof(numbuf), "%08lx", hash);
|
|
GWEN_DB_SetCharValue(dbCert, GWEN_DB_FLAGS_DEFAULT,
|
|
"hashValue", numbuf);
|
|
}
|
|
GWEN_DB_SetIntValue(dbCert, GWEN_DB_FLAGS_DEFAULT,
|
|
"isNew", isNew);
|
|
GWEN_DB_SetIntValue(dbCert, GWEN_DB_FLAGS_DEFAULT,
|
|
"isError", isError);
|
|
GWEN_DB_SetIntValue(dbCert, GWEN_DB_FLAGS_DEFAULT,
|
|
"isWarning", isWarning);
|
|
|
|
if (vr!=X509_V_OK) {
|
|
GWEN_NETTRANSPORTSSL_ASKADDCERT_RESULT res;
|
|
int isErr;
|
|
|
|
if (skd->secure) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN,
|
|
"Invalid peer certificate in secure mode, aborting");
|
|
GWEN_Socket_Close(skd->socket);
|
|
SSL_free(skd->ssl);
|
|
skd->ssl=0;
|
|
SSL_CTX_free(skd->ssl_ctx);
|
|
skd->ssl_ctx=0;
|
|
free(certbuf);
|
|
X509_free(cert);
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusDisabled);
|
|
return GWEN_NetTransportWorkResult_Error;
|
|
}
|
|
else {
|
|
DBG_INFO(GWEN_LOGDOMAIN,
|
|
"Invalid peer certificate, will ask user");
|
|
}
|
|
|
|
/* ask user */
|
|
isErr=0;
|
|
DBG_INFO(GWEN_LOGDOMAIN,
|
|
"Unknown certificate \"%s\", asking user",
|
|
certbuf);
|
|
res=GWEN_NetTransportSSL__AskAddCert(tr, dbCert);
|
|
switch(res) {
|
|
case GWEN_NetTransportSSL_AskAddCertResultError:
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Error asking user");
|
|
isErr=1;
|
|
break;
|
|
case GWEN_NetTransportSSL_AskAddCertResultNo:
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "User doesn't trust the certificate");
|
|
isErr=1;
|
|
break;
|
|
case GWEN_NetTransportSSL_AskAddCertResultTmp:
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Temporarily trusting certificate");
|
|
break;
|
|
case GWEN_NetTransportSSL_AskAddCertResultPerm:
|
|
DBG_NOTICE(GWEN_LOGDOMAIN, "Adding certificate to trusted certs");
|
|
if (GWEN_NetTransportSSL__SaveCert(tr, cert, skd->CAdir, !isNew)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Error saving certificate");
|
|
isErr=1;
|
|
}
|
|
break;
|
|
case GWEN_NetTransportSSL_AskAddCertResultIncoming:
|
|
if (!skd->newCAdir) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "No dir for incoming connections given");
|
|
isErr=1;
|
|
}
|
|
else {
|
|
DBG_NOTICE(GWEN_LOGDOMAIN, "Adding certificate to incoming certs");
|
|
if (GWEN_NetTransportSSL__SaveCert(tr, cert, skd->newCAdir, 1)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Error saving certificate");
|
|
isErr=1;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Unexpected result");
|
|
break;
|
|
} /* switch */
|
|
|
|
if (isErr) {
|
|
GWEN_Socket_Close(skd->socket);
|
|
SSL_free(skd->ssl);
|
|
skd->ssl=0;
|
|
SSL_CTX_free(skd->ssl_ctx);
|
|
skd->ssl_ctx=0;
|
|
free(certbuf);
|
|
X509_free(cert);
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusDisabled);
|
|
return GWEN_NetTransportWorkResult_Error;
|
|
}
|
|
}
|
|
else {
|
|
/* store peer certificate */
|
|
skd->peerCertificate=GWEN_NetTransportSSL__Cert2Db(cert);
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Certificate of peer \"%s\" is valid",
|
|
GWEN_DB_GetCharValue(skd->peerCertificate,
|
|
"commonName", 0,
|
|
"<nobody>"));
|
|
skd->isSecure=1;
|
|
}
|
|
free(certbuf);
|
|
X509_free(cert);
|
|
}
|
|
|
|
DBG_INFO(GWEN_LOGDOMAIN, "SSL connection established (%s)",
|
|
(skd->isSecure)?"verified":"not verified");
|
|
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusLConnected);
|
|
return GWEN_NetTransportWorkResult_Change;
|
|
}
|
|
|
|
case GWEN_NetTransportStatusListening: {
|
|
GWEN_SOCKET *newS;
|
|
GWEN_INETADDRESS *iaddr;
|
|
GWEN_NETTRANSPORT *newTr;
|
|
char addrBuffer[128];
|
|
|
|
DBG_VERBOUS(GWEN_LOGDOMAIN, "Listening");
|
|
if (GWEN_NetTransport_GetIncomingCount(tr)+1<
|
|
GWEN_NetTransport_GetBackLog(tr)) {
|
|
newS=0;
|
|
iaddr=0;
|
|
/* accept new connection (if there is any) */
|
|
err=GWEN_Socket_Accept(skd->socket, &iaddr, &newS);
|
|
if (!GWEN_Error_IsOk(err)) {
|
|
GWEN_InetAddr_free(iaddr);
|
|
GWEN_Socket_free(newS);
|
|
/* no new connection, due to an error ? */
|
|
if (GWEN_Error_GetType(err)!=
|
|
GWEN_Error_FindType(GWEN_SOCKET_ERROR_TYPE) ||
|
|
(GWEN_Error_GetCode(err)!=GWEN_SOCKET_ERROR_TIMEOUT &&
|
|
GWEN_Error_GetCode(err)!=GWEN_SOCKET_ERROR_INTERRUPTED)) {
|
|
/* jepp, there was an error */
|
|
DBG_INFO_ERR(GWEN_LOGDOMAIN, err);
|
|
return GWEN_NetTransportWorkResult_Error;
|
|
}
|
|
/* otherwise there simply is no waiting connection */
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "No incoming connection");
|
|
return GWEN_NetTransportWorkResult_NoChange;
|
|
}
|
|
|
|
/* we have an incoming connection */
|
|
GWEN_InetAddr_GetAddress(iaddr, addrBuffer, sizeof(addrBuffer));
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Incoming connection from %s (port %d)",
|
|
addrBuffer, GWEN_InetAddr_GetPort(iaddr));
|
|
|
|
/* set socket nonblocking */
|
|
err=GWEN_Socket_SetBlocking(newS, 0);
|
|
if (!GWEN_Error_IsOk(err)) {
|
|
DBG_ERROR_ERR(GWEN_LOGDOMAIN, err);
|
|
GWEN_InetAddr_free(iaddr);
|
|
GWEN_Socket_free(newS);
|
|
return GWEN_NetTransportWorkResult_Error;
|
|
}
|
|
|
|
/* create new transport layer, let it take over the new socket */
|
|
newTr=GWEN_NetTransportSSL_new(newS,
|
|
skd->CAdir,
|
|
skd->newCAdir,
|
|
skd->ownCertFile,
|
|
skd->dhfile,
|
|
skd->secure,
|
|
1);
|
|
GWEN_NetTransport_SetPeerAddr(newTr, iaddr);
|
|
GWEN_InetAddr_free(iaddr);
|
|
GWEN_NetTransport_SetLocalAddr(newTr, GWEN_NetTransport_GetLocalAddr(tr));
|
|
/* set status (already fully connected) */
|
|
GWEN_NetTransport_SetStatus(newTr, GWEN_NetTransportStatusPConnected);
|
|
/* set flags: This connection is a passive one */
|
|
GWEN_NetTransport_SetFlags(newTr,
|
|
GWEN_NetTransport_GetFlags(newTr) |
|
|
GWEN_NETTRANSPORT_FLAGS_PASSIVE);
|
|
|
|
/* add it to queue of incoming connections */
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
GWEN_NetTransport_AddNextIncoming(tr, newTr);
|
|
return GWEN_NetTransportWorkResult_Change;
|
|
}
|
|
else {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Too many incoming connections waiting");
|
|
}
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
break;
|
|
}
|
|
|
|
case GWEN_NetTransportStatusLConnected:
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Active connection, nothing to do");
|
|
/* TODO: check whether read/write are possible, return code
|
|
* accordingly */
|
|
break;
|
|
|
|
case GWEN_NetTransportStatusLDisconnecting: {
|
|
rv=SSL_shutdown(skd->ssl);
|
|
if (rv==1 || rv==-1) {
|
|
/* connection closed */
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Connection closed");
|
|
GWEN_Socket_Close(skd->socket);
|
|
SSL_free(skd->ssl);
|
|
skd->ssl=0;
|
|
SSL_CTX_free(skd->ssl_ctx);
|
|
skd->ssl_ctx=0;
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusPDisconnected);
|
|
return GWEN_NetTransportWorkResult_Change;
|
|
}
|
|
else {
|
|
int sslerr;
|
|
|
|
/* check for timeout (maximum: 5 seconds) */
|
|
if (GWEN_NetTransport_GetIdleTime(tr)>5) {
|
|
DBG_NOTICE(GWEN_LOGDOMAIN, "Forcing connection close");
|
|
GWEN_Socket_Close(skd->socket);
|
|
SSL_free(skd->ssl);
|
|
skd->ssl=0;
|
|
SSL_CTX_free(skd->ssl_ctx);
|
|
skd->ssl_ctx=0;
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusPDisconnected);
|
|
return GWEN_NetTransportWorkResult_Change;
|
|
}
|
|
|
|
/* check for error and act accordingly */
|
|
sslerr=SSL_get_error(skd->ssl, rv);
|
|
if (sslerr==SSL_ERROR_WANT_READ)
|
|
return GWEN_NetTransportResultWantRead;
|
|
else if (sslerr==SSL_ERROR_WANT_WRITE)
|
|
return GWEN_NetTransportResultWantWrite;
|
|
else {
|
|
if (sslerr==SSL_ERROR_SYSCALL && errno==0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Connection just went down (%d: %s)",
|
|
sslerr,
|
|
GWEN_NetTransportSSL_ErrorString(sslerr));
|
|
}
|
|
else {
|
|
if (sslerr==SSL_ERROR_ZERO_RETURN) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Connection closed");
|
|
}
|
|
else {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "List of pending SSL errors:");
|
|
ERR_print_errors_fp(stderr); /* DEBUG */
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "SSL error: %s (%d)",
|
|
GWEN_NetTransportSSL_ErrorString(sslerr),
|
|
sslerr);
|
|
}
|
|
}
|
|
|
|
GWEN_Socket_Close(skd->socket);
|
|
SSL_free(skd->ssl);
|
|
skd->ssl=0;
|
|
SSL_CTX_free(skd->ssl_ctx);
|
|
skd->ssl_ctx=0;
|
|
|
|
/* connection closed */
|
|
GWEN_NetTransport_SetStatus(tr, GWEN_NetTransportStatusPDisconnected);
|
|
GWEN_NetTransport_MarkActivity(tr);
|
|
return GWEN_NetTransportResultOk;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case GWEN_NetTransportStatusUnconnected:
|
|
case GWEN_NetTransportStatusDisabled:
|
|
case GWEN_NetTransportStatusPDisconnecting:
|
|
case GWEN_NetTransportStatusPDisconnected:
|
|
DBG_VERBOUS(GWEN_LOGDOMAIN, "Inactive connection (status \"%s\")",
|
|
GWEN_NetTransport_StatusName(st));
|
|
break;
|
|
|
|
default:
|
|
DBG_WARN(GWEN_LOGDOMAIN, "Hmm, status \"%s\" (%d) is unexpected...",
|
|
GWEN_NetTransport_StatusName(st),
|
|
st);
|
|
break;
|
|
} /* switch */
|
|
|
|
return GWEN_NetTransportWorkResult_NoChange;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
const char *GWEN_NetTransportSSL_ErrorString(unsigned int e) {
|
|
const char *s;
|
|
|
|
switch(e) {
|
|
case SSL_ERROR_NONE: s="SSL: None"; break;
|
|
#ifdef SSL_ERROR_ZERO_RETURN
|
|
case SSL_ERROR_ZERO_RETURN: s="SSL: connection closed"; break;
|
|
#endif
|
|
case SSL_ERROR_WANT_READ: s="SSL: Want to read"; break;
|
|
case SSL_ERROR_WANT_WRITE: s="SSL: Want to write"; break;
|
|
#ifdef SSL_ERROR_WANT_CONNECT
|
|
case SSL_ERROR_WANT_CONNECT: s="SSL: Want to connect"; break;
|
|
#endif
|
|
#ifdef SSL_ERROR_WANT_ACCEPT
|
|
case SSL_ERROR_WANT_ACCEPT: s="SSL: Want to accept"; break;
|
|
#endif
|
|
#ifdef SSL_ERROR_WANT_X509_LOOKUP
|
|
case SSL_ERROR_WANT_X509_LOOKUP: s="SSL: Want to lookup certificate"; break;
|
|
#endif
|
|
case SSL_ERROR_SYSCALL: s=strerror(errno); break;
|
|
case SSL_ERROR_SSL: s="SSL: protocol error"; break;
|
|
default: s="SSL: Unknown error"; break;
|
|
} /* switch */
|
|
return s;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
void GWEN_NetTransportSSL_SetCipherList(GWEN_NETTRANSPORT *tr,
|
|
const char *ciphers){
|
|
GWEN_NETTRANSPORTSSL *skd;
|
|
|
|
assert(tr);
|
|
skd=GWEN_INHERIT_GETDATA(GWEN_NETTRANSPORT, GWEN_NETTRANSPORTSSL, tr);
|
|
assert(skd);
|
|
|
|
free(skd->cipherList);
|
|
if (ciphers)
|
|
skd->cipherList=strdup(ciphers);
|
|
else
|
|
skd->cipherList=0;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
GWEN_DB_NODE *GWEN_NetTransportSSL_GetCipherList(){
|
|
STACK_OF(SSL_CIPHER) *ciphers;
|
|
SSL *ssl;
|
|
SSL_CTX *ctx;
|
|
const char *p;
|
|
|
|
ctx=SSL_CTX_new(SSLv23_client_method());
|
|
ssl=SSL_new(ctx);
|
|
|
|
ciphers=(STACK_OF(SSL_CIPHER)*)SSL_get_ciphers(ssl);
|
|
if (ciphers) {
|
|
GWEN_DB_NODE *dbCiphers;
|
|
SSL_CIPHER *curr;
|
|
int n;
|
|
char buffer[256];
|
|
|
|
dbCiphers=GWEN_DB_Group_new("ciphers");
|
|
for (n=0;n<sk_SSL_CIPHER_num(ciphers) ; n++) {
|
|
curr=sk_SSL_CIPHER_value(ciphers, n);
|
|
p=SSL_CIPHER_get_name(curr);
|
|
if (p) {
|
|
GWEN_DB_NODE *dbCurr;
|
|
|
|
dbCurr=GWEN_DB_GetGroup(dbCiphers, GWEN_PATH_FLAGS_CREATE_GROUP,
|
|
"cipher");
|
|
GWEN_DB_SetCharValue(dbCurr, GWEN_DB_FLAGS_DEFAULT,
|
|
"name", p);
|
|
GWEN_DB_SetIntValue(dbCurr, GWEN_DB_FLAGS_DEFAULT,
|
|
"bits", SSL_CIPHER_get_bits(curr, 0));
|
|
p=SSL_CIPHER_get_version(curr);
|
|
if (p)
|
|
GWEN_DB_SetCharValue(dbCurr, GWEN_DB_FLAGS_DEFAULT,
|
|
"version", p);
|
|
p=SSL_CIPHER_description(curr, buffer, sizeof(buffer));
|
|
if (p)
|
|
GWEN_DB_SetCharValue(dbCurr, GWEN_DB_FLAGS_DEFAULT,
|
|
"description", p);
|
|
} /* if cipher name */
|
|
} /* for */
|
|
SSL_free(ssl);
|
|
SSL_CTX_free(ctx);
|
|
return dbCiphers;
|
|
} /* if ciphers */
|
|
else {
|
|
DBG_WARN(GWEN_LOGDOMAIN, "No ciphers");
|
|
SSL_free(ssl);
|
|
SSL_CTX_free(ctx);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
int GWEN_NetTransportSSL_GenerateCertAndKeyFile(const char *fname,
|
|
int bits,
|
|
int serial,
|
|
int days,
|
|
GWEN_DB_NODE *db) {
|
|
X509 *x;
|
|
EVP_PKEY *pk;
|
|
RSA *rsa;
|
|
X509_NAME *name=NULL;
|
|
X509_NAME_ENTRY *ne=NULL;
|
|
FILE *f;
|
|
const char *p;
|
|
|
|
X509V3_add_standard_extensions();
|
|
|
|
pk=EVP_PKEY_new();
|
|
if (!pk){
|
|
fprintf(stderr, "Could not create RSA key\n");
|
|
EVP_PKEY_free(pk);
|
|
X509V3_EXT_cleanup();
|
|
return -1;
|
|
}
|
|
|
|
x=X509_new();
|
|
if (!x) {
|
|
fprintf(stderr, "Could not create certificate\n");
|
|
EVP_PKEY_free(pk);
|
|
X509V3_EXT_cleanup();
|
|
return -1;
|
|
}
|
|
|
|
rsa=RSA_generate_key(bits, RSA_F4, NULL, NULL);
|
|
if (!EVP_PKEY_assign_RSA(pk, rsa)){
|
|
fprintf(stderr, "Could not assign RSA key\n");
|
|
X509_free(x);
|
|
EVP_PKEY_free(pk);
|
|
RSA_free(rsa);
|
|
X509V3_EXT_cleanup();
|
|
return -1;
|
|
}
|
|
rsa=NULL;
|
|
|
|
X509_set_version(x,3);
|
|
ASN1_INTEGER_set(X509_get_serialNumber(x),serial);
|
|
X509_gmtime_adj(X509_get_notBefore(x),0);
|
|
if (!days)
|
|
days=GWEN_NETTRANSPORTSSL_DEFAULT_CERT_DAYS;
|
|
X509_gmtime_adj(X509_get_notAfter(x),(long)60*60*24*days);
|
|
X509_set_pubkey(x, pk);
|
|
|
|
name=X509_NAME_new();
|
|
|
|
p=GWEN_DB_GetCharValue(db, "countryName", 0, "DE");
|
|
ne=X509_NAME_ENTRY_create_by_NID(NULL,
|
|
NID_countryName,
|
|
V_ASN1_APP_CHOOSE,
|
|
(unsigned char*)p,
|
|
-1);
|
|
X509_NAME_add_entry(name, ne, 0, 0);
|
|
|
|
p=GWEN_DB_GetCharValue(db, "commonName", 0, 0);
|
|
if (p) {
|
|
X509_NAME_ENTRY_create_by_NID(&ne, NID_commonName,
|
|
V_ASN1_APP_CHOOSE,
|
|
(unsigned char*)p,
|
|
-1);
|
|
X509_NAME_add_entry(name, ne, -1, 0);
|
|
}
|
|
|
|
p=GWEN_DB_GetCharValue(db, "organizationName", 0, 0);
|
|
if (p) {
|
|
X509_NAME_ENTRY_create_by_NID(&ne, NID_organizationName,
|
|
V_ASN1_APP_CHOOSE,
|
|
(unsigned char*)p,
|
|
-1);
|
|
X509_NAME_add_entry(name, ne, -1, 0);
|
|
}
|
|
|
|
p=GWEN_DB_GetCharValue(db, "organizationalUnitName", 0, 0);
|
|
if (p) {
|
|
X509_NAME_ENTRY_create_by_NID(&ne, NID_organizationalUnitName,
|
|
V_ASN1_APP_CHOOSE,
|
|
(unsigned char*)p,
|
|
-1);
|
|
X509_NAME_add_entry(name, ne, -1, 0);
|
|
}
|
|
|
|
p=GWEN_DB_GetCharValue(db, "localityName", 0, 0);
|
|
if (p) {
|
|
X509_NAME_ENTRY_create_by_NID(&ne, NID_localityName,
|
|
V_ASN1_APP_CHOOSE,
|
|
(unsigned char*)p,
|
|
-1);
|
|
X509_NAME_add_entry(name, ne, -1, 0);
|
|
}
|
|
|
|
p=GWEN_DB_GetCharValue(db, "stateOrProvinceName", 0, 0);
|
|
if (p) {
|
|
X509_NAME_ENTRY_create_by_NID(&ne, NID_stateOrProvinceName,
|
|
V_ASN1_APP_CHOOSE,
|
|
(unsigned char*)p,
|
|
-1);
|
|
X509_NAME_add_entry(name, ne, -1, 0);
|
|
}
|
|
|
|
X509_set_subject_name(x, name);
|
|
X509_set_issuer_name(x, name);
|
|
|
|
/* finished with structure */
|
|
X509_NAME_ENTRY_free(ne);
|
|
X509_NAME_free(name);
|
|
|
|
if (!X509_sign(x, pk, EVP_md5())) {
|
|
fprintf(stderr, "Could not sign\n");
|
|
X509_free(x);
|
|
EVP_PKEY_free(pk);
|
|
X509V3_EXT_cleanup();
|
|
return -1;
|
|
}
|
|
|
|
/* save key */
|
|
f=fopen(fname, "w+");
|
|
if (!f) {
|
|
fprintf(stderr, "Could not save private key\n");
|
|
X509_free(x);
|
|
EVP_PKEY_free(pk);
|
|
X509V3_EXT_cleanup();
|
|
return -1;
|
|
}
|
|
PEM_write_RSAPrivateKey(f,
|
|
pk->pkey.rsa,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
NULL);
|
|
/* save cert */
|
|
PEM_write_X509(f, x);
|
|
if (fclose(f)) {
|
|
fprintf(stderr, "Could not close file\n");
|
|
X509_free(x);
|
|
EVP_PKEY_free(pk);
|
|
X509V3_EXT_cleanup();
|
|
return -1;
|
|
}
|
|
X509_free(x);
|
|
EVP_PKEY_free(pk);
|
|
X509V3_EXT_cleanup();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
void GWEN_NetTransportSSL__GenerateDhFile_Callback(int i, int j, void *p) {
|
|
GWEN_WAITCALLBACK_RESULT res;
|
|
|
|
switch(i) {
|
|
case 0:
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Generated %d. potential prime number", j);
|
|
break;
|
|
case 1:
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Testing %d. prime number", j);
|
|
break;
|
|
case 2:
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Prime found in %d. try", j);
|
|
break;
|
|
} /* switch */
|
|
|
|
res=GWEN_WaitCallback();
|
|
if (res!=GWEN_WaitCallbackResult_Continue) {
|
|
DBG_WARN(GWEN_LOGDOMAIN, "User wants to abort, but this function can not be aborted");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------- FUNCTION */
|
|
int GWEN_NetTransportSSL_GenerateDhFile(const char *fname, int bits) {
|
|
DH *dh;
|
|
FILE *f;
|
|
|
|
#ifdef GWEN_RANDOM_DEVICE
|
|
if (!RAND_load_file(GWEN_RANDOM_DEVICE, 40)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Could not seed random (maybe \"%s\" is missing?)",
|
|
GWEN_RANDOM_DEVICE);
|
|
return -1;
|
|
}
|
|
#endif
|
|
dh=DH_generate_parameters(bits,
|
|
2,
|
|
GWEN_NetTransportSSL__GenerateDhFile_Callback,
|
|
0);
|
|
if (!dh) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Could not generate DH parameters");
|
|
return -1;
|
|
}
|
|
|
|
f=fopen(fname, "w+");
|
|
if (!f) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "fopen(%s): %s", fname, strerror(errno));
|
|
DH_free(dh);
|
|
return -1;
|
|
}
|
|
|
|
if (!PEM_write_DHparams(f, dh)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Could not write DH params");
|
|
fclose(f);
|
|
DH_free(dh);
|
|
return -1;
|
|
}
|
|
|
|
if (fclose(f)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "fclose(%s): %s", fname, strerror(errno));
|
|
DH_free(dh);
|
|
return -1;
|
|
}
|
|
|
|
DBG_INFO(GWEN_LOGDOMAIN, "DH params generated and written");
|
|
DH_free(dh);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|