Not meaning to come across as rude, but am I (and everyone else who has tried) making some basic mistake? I originally started using gnutls in an attempt to add SSL support to an IRCd (InspIRCd). Beginning with the version I had installed (The debian package was versioned 1.0.16-14) I tried the examples in the manual, which didn't compile which I thought was because of the docs being for 1.3.3 and me having an old version. I then installed 1.3.3 from source which, while allowing the examples in the docs to compile, I couldn't get it to successfully connect using either my ircd code, the examples in the docs (the X.509 client and the X.509 echo server (I and II)), or the commandline tools gnutls-cli, guntls-cli-debug and gnutls-serv. Everything failed with the same error:
Could not negotiate a supported cipher suite (GNUTLS_E_UNKNOWN_CIPHER_SUITE). Suspecting that my Debian install (with multiple versions on the same system) was to blame I tried the commandline tools and my ircd code on another box running FreeBSD and 1.3.(2 or 3) and a friend tried the commandline tools on a Gentoo box with 1.2.3 with the same result. Attached are the source files from the docs I was compiling, and the (very, very alpha) ircd module. Cheers, -ol -- I will live forever, or die trying.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <gnutls/gnutls.h>
/* A very basic TLS client, with X.509 authentication.
*/
#define PORT 5556
#define MAX_BUF 1024
#define CAFILE "ca.pem"
#define MSG "GET / HTTP/1.0\r\n\r\n"
int tcp_connect()
{
struct sockaddr_in info;
struct in_addr addy;
int fd;
if((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("Error %d creating socket: %s\n", errno, strerror(errno));
return -1;
}
else
{
printf("Socket created successfully\n");
memset(&info, '\0', sizeof(info));
inet_aton("127.0.0.1", &addy);
info.sin_family = AF_INET;
info.sin_addr = addy;
info.sin_port = htons(PORT);
if(connect(fd, (sockaddr*)&info, sizeof(info)) == -1)
{
printf("Error %d connecting socket: %s\n", errno, strerror(errno));
return -1;
}
else
{
printf("Socket connected successfully\n");
return fd;
}
}
}
void tcp_close (int fd)
{
shutdown(fd, 2);
close(fd);
}
int main(void)
{
int ret, sd, ii;
gnutls_session_t session;
char buffer[MAX_BUF + 1];
gnutls_certificate_credentials_t xcred;
/* Allow connections to servers that have OpenPGP keys as well.
*/
const int cert_type_priority[3] = { GNUTLS_CRT_X509,
GNUTLS_CRT_OPENPGP, 0
};
gnutls_global_init ();
/* X509 stuff */
gnutls_certificate_allocate_credentials (&xcred);
/* sets the trusted cas file
*/
gnutls_certificate_set_x509_trust_file (xcred, CAFILE, GNUTLS_X509_FMT_PEM);
/* Initialize TLS session
*/
gnutls_init (&session, GNUTLS_CLIENT);
/* Use default priorities */
gnutls_set_default_priority (session);
gnutls_certificate_type_set_priority (session, cert_type_priority);
/* put the x509 credentials to the current session
*/
gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, xcred);
/* connect to the peer
*/
sd = tcp_connect ();
gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t) sd);
/* Perform the TLS handshake
*/
ret = gnutls_handshake (session);
if (ret < 0)
{
fprintf (stderr, "*** Handshake failed\n");
gnutls_perror (ret);
goto end;
}
else
{
printf ("- Handshake was completed\n");
}
gnutls_record_send (session, MSG, strlen (MSG));
ret = gnutls_record_recv (session, buffer, MAX_BUF);
if (ret == 0)
{
printf ("- Peer has closed the TLS connection\n");
goto end;
}
else if (ret < 0)
{
fprintf (stderr, "*** Error: %s\n", gnutls_strerror (ret));
goto end;
}
printf ("- Received %d bytes: ", ret);
for (ii = 0; ii < ret; ii++)
{
fputc (buffer[ii], stdout);
}
fputs ("\n", stdout);
gnutls_bye (session, GNUTLS_SHUT_RDWR);
end:
tcp_close (sd);
gnutls_deinit (session);
gnutls_certificate_free_credentials (xcred);
gnutls_global_deinit ();
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <gnutls/gnutls.h>
#define KEYFILE "key.pem"
#define CERTFILE "cert.pem"
#define CAFILE "ca.pem"
#define CRLFILE "crl.pem"
/* This is a sample TLS 1.0 echo server.
* Export-grade ciphersuites and session resuming are supported.
*/
#define SA struct sockaddr
#define SOCKET_ERR(err,s) if(err==-1) {perror(s);return(1);}
#define MAX_BUF 1024
#define PORT 5556 /* listen to 5556 port */
#define DH_BITS 1024
/* These are global */
gnutls_certificate_credentials_t cert_cred;
static void wrap_db_init (void);
static void wrap_db_deinit (void);
static int wrap_db_store (void *dbf, gnutls_datum_t key, gnutls_datum_t data);
static gnutls_datum_t wrap_db_fetch (void *dbf, gnutls_datum_t key);
static int wrap_db_delete (void *dbf, gnutls_datum_t key);
#define TLS_SESSION_CACHE 50
gnutls_session_t
initialize_tls_session (void)
{
gnutls_session_t session;
gnutls_init (&session, GNUTLS_SERVER);
/* Use the default priorities, plus, export cipher suites.
*/
gnutls_set_default_export_priority (session);
gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, cert_cred);
/* request client certificate if any.
*/
gnutls_certificate_server_set_request (session, GNUTLS_CERT_REQUEST);
gnutls_dh_set_prime_bits (session, DH_BITS);
if (TLS_SESSION_CACHE != 0)
{
gnutls_db_set_retrieve_function (session, wrap_db_fetch);
gnutls_db_set_remove_function (session, wrap_db_delete);
gnutls_db_set_store_function (session, wrap_db_store);
gnutls_db_set_ptr (session, NULL);
}
return session;
}
gnutls_dh_params_t dh_params;
/* Export-grade cipher suites require temporary RSA
* keys.
*/
gnutls_rsa_params_t rsa_params;
int
generate_dh_params (void)
{
/* Generate Diffie Hellman parameters - for use with DHE
* kx algorithms. These should be discarded and regenerated
* once a day, once a week or once a month. Depends on the
* security requirements.
*/
gnutls_dh_params_init (&dh_params);
gnutls_dh_params_generate2 (dh_params, DH_BITS);
return 0;
}
static int
generate_rsa_params (void)
{
gnutls_rsa_params_init (&rsa_params);
/* Generate RSA parameters - for use with RSA-export
* cipher suites. These should be discarded and regenerated
* once a day, once every 500 transactions etc. Depends on the
* security requirements.
*/
gnutls_rsa_params_generate2 (rsa_params, 512);
return 0;
}
int
main (void)
{
int err, listen_sd, i;
int sd, ret;
struct sockaddr_in sa_serv;
struct sockaddr_in sa_cli;
int client_len;
char topbuf[512];
gnutls_session_t session;
char buffer[MAX_BUF + 1];
int optval = 1;
char name[256];
strcpy (name, "Echo Server");
/* this must be called once in the program
*/
gnutls_global_init ();
gnutls_certificate_allocate_credentials (&cert_cred);
gnutls_certificate_set_x509_trust_file (cert_cred, CAFILE,
GNUTLS_X509_FMT_PEM);
gnutls_certificate_set_x509_crl_file (cert_cred, CRLFILE,
GNUTLS_X509_FMT_PEM);
gnutls_certificate_set_x509_key_file (cert_cred, CERTFILE, KEYFILE,
GNUTLS_X509_FMT_PEM);
generate_dh_params ();
generate_rsa_params ();
if (TLS_SESSION_CACHE != 0)
{
wrap_db_init ();
}
gnutls_certificate_set_dh_params (cert_cred, dh_params);
gnutls_certificate_set_rsa_export_params (cert_cred, rsa_params);
/* Socket operations
*/
listen_sd = socket (AF_INET, SOCK_STREAM, 0);
SOCKET_ERR (listen_sd, "socket");
memset (&sa_serv, '\0', sizeof (sa_serv));
sa_serv.sin_family = AF_INET;
sa_serv.sin_addr.s_addr = INADDR_ANY;
sa_serv.sin_port = htons (PORT); /* Server Port number */
setsockopt (listen_sd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof (int));
err = bind (listen_sd, (SA *) & sa_serv, sizeof (sa_serv));
SOCKET_ERR (err, "bind");
err = listen (listen_sd, 1024);
SOCKET_ERR (err, "listen");
printf ("%s ready. Listening to port '%d'.\n\n", name, PORT);
client_len = sizeof (sa_cli);
for (;;)
{
session = initialize_tls_session ();
sd = accept (listen_sd, (SA *) & sa_cli, (socklen_t*)&client_len);
printf ("- connection from %s, port %d\n",
inet_ntop (AF_INET, &sa_cli.sin_addr, topbuf,
sizeof (topbuf)), ntohs (sa_cli.sin_port));
gnutls_transport_set_ptr (session, (gnutls_transport_ptr_t) sd);
ret = gnutls_handshake (session);
if (ret < 0)
{
close (sd);
gnutls_deinit (session);
fprintf (stderr, "*** Handshake has failed (%s)\n\n",
gnutls_strerror (ret));
continue;
}
printf ("- Handshake was completed\n");
/* print_info(session); */
i = 0;
for (;;)
{
memset (buffer, 0, MAX_BUF + 1);
ret = gnutls_record_recv (session, buffer, MAX_BUF);
if (ret == 0)
{
printf ("\n- Peer has closed the TLS connection\n");
break;
}
else if (ret < 0)
{
fprintf (stderr, "\n*** Received corrupted "
"data(%d). Closing the connection.\n\n", ret);
break;
}
else if (ret > 0)
{
/* echo data back to the client
*/
gnutls_record_send (session, buffer, strlen (buffer));
}
}
printf ("\n");
/* do not wait for the peer to close the connection.
*/
gnutls_bye (session, GNUTLS_SHUT_WR);
close (sd);
gnutls_deinit (session);
}
close (listen_sd);
gnutls_certificate_free_credentials (cert_cred);
gnutls_global_deinit ();
return 0;
}
/* Functions and other stuff needed for session resuming.
* This is done using a very simple list which holds session ids
* and session data.
*/
#define MAX_SESSION_ID_SIZE 32
#define MAX_SESSION_DATA_SIZE 512
typedef struct
{
char session_id[MAX_SESSION_ID_SIZE];
int session_id_size;
char session_data[MAX_SESSION_DATA_SIZE];
int session_data_size;
} CACHE;
static CACHE *cache_db;
static int cache_db_ptr = 0;
static void
wrap_db_init (void)
{
/* allocate cache_db */
cache_db = (CACHE*)calloc(1, TLS_SESSION_CACHE * sizeof(CACHE));
}
static void
wrap_db_deinit (void)
{
return;
}
static int
wrap_db_store (void *dbf, gnutls_datum_t key, gnutls_datum_t data)
{
if (cache_db == NULL)
return -1;
if (key.size > MAX_SESSION_ID_SIZE)
return -1;
if (data.size > MAX_SESSION_DATA_SIZE)
return -1;
memcpy (cache_db[cache_db_ptr].session_id, key.data, key.size);
cache_db[cache_db_ptr].session_id_size = key.size;
memcpy (cache_db[cache_db_ptr].session_data, data.data, data.size);
cache_db[cache_db_ptr].session_data_size = data.size;
cache_db_ptr++;
cache_db_ptr %= TLS_SESSION_CACHE;
return 0;
}
static gnutls_datum_t
wrap_db_fetch (void *dbf, gnutls_datum_t key)
{
gnutls_datum_t res = { NULL, 0 };
int i;
if (cache_db == NULL)
return res;
for (i = 0; i < TLS_SESSION_CACHE; i++)
{
if (key.size == cache_db[i].session_id_size &&
memcmp (key.data, cache_db[i].session_id, key.size) == 0)
{
res.size = cache_db[i].session_data_size;
res.data = (unsigned char*)gnutls_malloc(res.size);
if (res.data == NULL)
return res;
memcpy (res.data, cache_db[i].session_data, res.size);
return res;
}
}
return res;
}
static int
wrap_db_delete (void *dbf, gnutls_datum_t key)
{
int i;
if (cache_db == NULL)
return -1;
for (i = 0; i < TLS_SESSION_CACHE; i++)
{
if (key.size == cache_db[i].session_id_size &&
memcmp (key.data, cache_db[i].session_id, key.size) == 0)
{
cache_db[i].session_id_size = 0;
cache_db[i].session_data_size = 0;
return 0;
}
}
return -1;
}
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <gnutls/gnutls.h>
#include "users.h"
#include "channels.h"
#include "modules.h"
#include "helperfuncs.h"
#include "socket.h"
#include "hashcomp.h"
/* $ModDesc: Provides SSL support for clients */
/* $CompileFlags: `libgnutls-config --cflags` */
/* $LinkerFlags: -L/usr/local/lib -Wl,--rpath -Wl,/usr/local/lib -L/usr/lib -Wl,--rpath -Wl,/usr/lib -lgnutls */
#define KEYFILE "key.pem"
#define CERTFILE "cert.pem"
#define CAFILE "ca.pem"
#define CRLFILE "crl.pem"
#define DH_BITS 1024
enum issl_status { ISSL_NONE, ISSL_HANDSHAKING_READ, ISSL_HANDSHAKING_WRITE, ISSL_HANDSHAKEN, ISSL_CLOSING, ISSL_CLOSED };
class issl_session
{
public:
gnutls_session_t sess;
issl_status status;
std::string outbuf;
int fd;
};
class ModuleSSL : public Module
{
Server* Srv;
ServerConfig* SrvConf;
issl_session sessions[MAX_DESCRIPTORS];
gnutls_certificate_credentials x509_cred;
gnutls_dh_params dh_params;
public:
ModuleSSL(Server* Me)
: Module::Module(Me)
{
Srv = Me;
SrvConf = Srv->GetConfig();
SrvConf->AddIOHook(6661, this);
gnutls_global_init(); // This must be called once in the program
gnutls_certificate_allocate_credentials(&x509_cred);
gnutls_certificate_set_x509_trust_file(x509_cred, CAFILE, GNUTLS_X509_FMT_PEM);
gnutls_certificate_set_x509_crl_file (x509_cred, CRLFILE, GNUTLS_X509_FMT_PEM);
gnutls_certificate_set_x509_key_file (x509_cred, CERTFILE, KEYFILE, GNUTLS_X509_FMT_PEM);
generate_dh_params();
gnutls_certificate_set_dh_params(x509_cred, dh_params);
}
virtual ~ModuleSSL()
{
gnutls_certificate_free_credentials(x509_cred);
gnutls_global_deinit();
}
virtual Version GetVersion()
{
return Version(1, 0, 0, 0, 0);
}
void Implements(char* List)
{
List[I_OnRawSocketAccept] = List[I_OnRawSocketClose] = List[I_OnRawSocketRead] = List[I_OnRawSocketWrite] = 1;
List[I_OnSyncUserMetaData] = List[I_OnDecodeMetaData] = List[I_OnUserQuit] = 1;
}
virtual void OnRawSocketAccept(int fd, std::string ip, int localport)
{
issl_session* session = &sessions[fd];
session->fd = fd;
gnutls_init(&session->sess, GNUTLS_SERVER);
gnutls_set_default_priority(session->sess); // Avoid calling all the priority functions, defaults are adequate.
gnutls_credentials_set(session->sess, GNUTLS_CRD_CERTIFICATE, x509_cred);
gnutls_certificate_server_set_request(session->sess, GNUTLS_CERT_REQUEST); // Request client certificate if any.
gnutls_dh_set_prime_bits(session->sess, DH_BITS);
gnutls_transport_set_ptr(session->sess, (gnutls_transport_ptr_t) fd); // Give gnutls the fd for the socket.
Handshake(session);
}
virtual void OnRawSocketClose(int fd)
{
CloseSession(&sessions[fd]);
}
virtual int OnRawSocketRead(int fd, char* buffer, unsigned int count, int &readresult)
{
issl_session* session = &sessions[fd];
if(session->status == ISSL_HANDSHAKING_WRITE || session->status == ISSL_HANDSHAKING_READ)
{
// The handshake isn't finished, try to finish it.
if((session->status == ISSL_HANDSHAKING_READ) && Handshake(session))
{
// Handshake successfully resumed.
log(DEBUG, "m_ssl.so: OnRawSocketRead(): successfully resumed handshake");
}
else
{
// If the handshake still isn't finished, don't try and write anything.
if(session->status == ISSL_HANDSHAKING_WRITE)
log(DEBUG, "m_ssl.so: OnRawSocketRead(): handshake wants to write data but we are currently reading");
else
log(DEBUG, "m_ssl.so: OnRawSocketRead(): failed to resume handshake");
// If the handshake isn't complete I don't think we can read with gnutls_record_recv(),
// so I don't think we can do anything.
return 1;
}
}
// Is this right? Not sure if the unencrypted data is garaunteed to be the same length.
int ret = gnutls_record_recv(session->sess, buffer, count);
if(ret == 0)
{
// Client closed connection.
log(DEBUG, "m_ssl: Client closed the connection");
}
else if(ret < 0)
{
if(ret != GNUTLS_E_AGAIN && ret != GNUTLS_E_INTERRUPTED)
{
log(DEBUG, "m_ssl.so: Error reading SSL data: %s", gnutls_strerror(ret));
}
else
{
log(DEBUG, "m_ssl.so: Not all SSL data read: %s", gnutls_strerror(ret));
}
}
else if (ret > 0)
{
readresult = ret;
}
return 1;
}
virtual int OnRawSocketWrite(int fd, char* buffer, int count)
{
issl_session* session = &sessions[fd];
const char* sendbuffer = buffer;
if(session->status == ISSL_HANDSHAKING_WRITE || session->status == ISSL_HANDSHAKING_READ)
{
// The handshake isn't finished, try to finish it.
if((session->status == ISSL_HANDSHAKING_WRITE) && Handshake(session))
{
// Handshake successfully resumed.
log(DEBUG, "m_ssl.so: OnRawSocketWrite(): successfully resumed handshake");
}
else
{
// If the handshake still isn't finished, don't try and write anything.
if(session->status == ISSL_HANDSHAKING_READ)
log(DEBUG, "m_ssl.so: OnRawSocketWrite(): handshake wants to read data but we are currently writing");
else
log(DEBUG, "m_ssl.so: OnRawSocketWrite(): failed to resume handshake");
// If the handshake isn't complete I don't think we can write with gnutls_record_send(),
// so I don't think there's any option other than buffering what insp wants us to send.
log(DEBUG, "m_ssl.so: OnRawSocketWrite(): buffering data as it cannot be written (handshake pending) Data: %s", sendbuffer);
session->outbuf.append(sendbuffer, count);
return 1;
}
}
if(session->sess)
{
if(session->outbuf.size())
{
// If there's stuff in the outgoing buffer..
session->outbuf.append(sendbuffer, count);
sendbuffer = session->outbuf.data();
count = session->outbuf.size();
}
int ret = gnutls_record_send(session->sess, sendbuffer, count);
if(ret < 0)
{
if(ret != GNUTLS_E_AGAIN && ret != GNUTLS_E_INTERRUPTED)
{
log(DEBUG, "m_ssl.so: Error writing SSL data: %s", gnutls_strerror(ret));
}
else
{
log(DEBUG, "m_ssl.so: Not all SSL data read: %s", gnutls_strerror(ret));
}
}
}
else
{
log(DEBUG, "m_ssl.so: No session to write to");
}
return 1;
}
virtual void OnUserQuit(userrec* user, std::string reason)
{
// Remove SSL flag
// Free SSL stuff
}
virtual void OnSyncUserMetaData(userrec* user, Module* proto, void* opaque, std::string extname)
{
// check if the linking module wants to know about OUR metadata
if(extname == "ssl")
{
// check if this user has an swhois field to send
if(user->GetExt(extname))
{
// call this function in the linking module, let it format the data how it
// sees fit, and send it on its way. We dont need or want to know how.
proto->ProtoSendMetaData(opaque, TYPE_USER, user, extname, "ON");
}
}
}
virtual void OnDecodeMetaData(int target_type, void* target, std::string extname, std::string extdata)
{
// check if its our metadata key, and its associated with a user
if ((target_type == TYPE_USER) && (extname == "ssl"))
{
userrec* dest = (userrec*)target;
// if they dont already have an swhois field, accept the remote server's
if (!dest->GetExt(extname))
{
dest->Extend(extname, "ON");
}
}
}
bool Handshake(issl_session* session)
{
int ret = gnutls_handshake(session->sess);
if(ret < 0)
{
if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
{
// Handshake needs resuming later, read() or write() would have blocked.
if(gnutls_record_get_direction(session->sess) == 0)
{
// gnutls_handshake() wants to read() again.
session->status = ISSL_HANDSHAKING_READ;
log(DEBUG, "m_ssl.so: Handshake needs resuming (reading) later, error string: %s", gnutls_strerror(ret));
}
else
{
// gnutls_handshake() wants to write() again.
session->status = ISSL_HANDSHAKING_WRITE;
log(DEBUG, "m_ssl.so: Handshake needs resuming (writing) later, error string: %s", gnutls_strerror(ret));
}
}
else
{
// Handshake failed.
CloseSession(session);
log(DEBUG, "m_ssl.so: Handshake failed, error string: %s", gnutls_strerror(ret));
session->status = ISSL_CLOSING;
}
return false;
}
else
{
// Handshake complete.
log(DEBUG, "m_ssl.so: Handshake completed");
session->status = ISSL_HANDSHAKEN;
return true;
}
}
void CloseSession(issl_session* session)
{
if(session->sess)
{
gnutls_bye(session->sess, GNUTLS_SHUT_WR);
gnutls_deinit(session->sess);
}
session->sess = NULL;
session->status = ISSL_NONE;
}
int generate_dh_params()
{
// Generate Diffie Hellman parameters - for use with DHE
// kx algorithms. These should be discarded and regenerated
// once a day, once a week or once a month. Depending on the
// security requirements.
gnutls_dh_params_init(&dh_params);
gnutls_dh_params_generate2(dh_params, DH_BITS);
return 0;
}
};
class ModuleSSLFactory : public ModuleFactory
{
public:
ModuleSSLFactory()
{
}
~ModuleSSLFactory()
{
}
virtual Module * CreateModule(Server* Me)
{
return new ModuleSSL(Me);
}
};
extern "C" void * init_module( void )
{
return new ModuleSSLFactory;
}
signature.asc
Description: PGP signature
_______________________________________________ Help-gnutls mailing list [email protected] http://lists.gnu.org/mailman/listinfo/help-gnutls
