I found a pretty good SSL/TLS server-client example in C and I wanted to adapt it for use with the BIO library. And I pretty much succeeded except one error I get when running the server:
$ ./ssl-server
68671:error:140950D3:SSL routines:SSL3_READ_N:read bio not set:/SourceCache/OpenSSL098/OpenSSL098-35.1/src/ssl/s3_pkt.c:203:
I’m using gcc -o ssl-server SSL-Server.c -lssl -lcrypto -Wall to compile the server:
//SSL-Server.c
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <resolv.h>
#include "openssl/ssl.h"
#include "openssl/err.h"
#define FAIL -1
#define PORT "2013"
SSL_CTX* InitServerCTX(void)
{
SSL_METHOD *method;
SSL_CTX *ctx;
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
method = SSLv3_server_method();
ctx = SSL_CTX_new(method);
if( ctx == NULL )
{
ERR_print_errors_fp(stderr);
abort();
}
return ctx;
}
void LoadCertificates(SSL_CTX* ctx, char* CertFile, char* KeyFile)
{
if( SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0 )
{
ERR_print_errors_fp(stderr);
abort();
}
if( SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0 )
{
ERR_print_errors_fp(stderr);
abort();
}
if( !SSL_CTX_check_private_key(ctx) )
{
fprintf(stderr, "Private key does not match the public certificate\n");
abort();
}
}
void ShowCerts(SSL* ssl)
{
X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl);
if ( cert != NULL )
{
printf("Server certificates:\n");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
printf("Subject: %s\n", line);
free(line);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
printf("Issuer: %s\n", line);
free(line);
X509_free(cert);
}
else
{
printf("No certificates.\n");
}
}
void Servlet(SSL* ssl)
{
char buf[1024];
int sd, bytes;
if( SSL_accept(ssl) == FAIL )
{
ERR_print_errors_fp(stderr);
}
else
{
ShowCerts(ssl);
bytes = SSL_read(ssl, buf, sizeof(buf));
if( bytes > 0 )
{
buf[bytes] = 0;
printf("Client msg: \"%s\"\n", buf);
SSL_write(ssl, "back message", strlen("back message"));
}
else
{
ERR_print_errors_fp(stderr);
}
}
sd = SSL_get_fd(ssl);
SSL_free(ssl);
close(sd);
}
int main(int count, char *strings[])
{
SSL_CTX *ctx;
BIO *acc, *client;
SSL_library_init();
ctx = InitServerCTX();
LoadCertificates(ctx, "mycert.pem", "mycert.pem");
acc = BIO_new_accept(PORT);
if(!acc)
{
printf("Error creating server socket");
}
while(1)
{
if(BIO_do_accept(acc) <= 0)
{
printf("Error binding server socket");
}
SSL *ssl;
client = BIO_pop(acc);
if(!(ssl = SSL_new(ctx)))
{
printf("Error creating SSL context");
}
SSL_set_bio(ssl, client, client);
// Here should be created threads
Servlet(ssl);
}
SSL_CTX_free(ctx);
}
I’m using gcc -o ssl-client SSL-Client.c -lssl -lcrypto -Wall to compile the client:
//SSL-Client.c
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <resolv.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#define FAIL -1
#define SERVER "localhost"
#define PORT "2013"
SSL_CTX* InitCTX(void)
{
SSL_METHOD *method;
SSL_CTX *ctx;
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
method = SSLv3_client_method();
ctx = SSL_CTX_new(method);
if( ctx == NULL )
{
ERR_print_errors_fp(stderr);
abort();
}
return ctx;
}
void ShowCerts(SSL* ssl)
{
X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl);
if( cert != NULL )
{
printf("Server certificates:\n");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
printf("Subject: %s\n", line);
free(line);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
printf("Issuer: %s\n", line);
free(line);
X509_free(cert);
}
else
{
printf("No certificates.\n");
}
}
int main(int count, char *strings[])
{
SSL_CTX *ctx;
SSL *ssl;
BIO *conn;
char buf[1024];
int bytes;
SSL_library_init();
ctx = InitCTX();
conn = BIO_new_connect(SERVER ":" PORT);
if(!conn)
{
printf("Error creating connection BIO");
}
if(BIO_do_connect(conn) <= 0)
{
printf("Error connecting to remote machine");
}
ssl = SSL_new(ctx);
SSL_set_bio(ssl, conn, conn);
if( SSL_connect(ssl) <= 0 )
{
printf("Error connecting SSL object");
}
else
{
printf("Connected!");
ShowCerts(ssl);
SSL_write(ssl, "ana are mere", strlen("ana are mere") );
bytes = SSL_read(ssl, buf, sizeof(buf));
printf("%s\n", buf);
SSL_free(ssl);
}
SSL_CTX_free(ctx);
return 0;
}
I’m generating the certificate with the openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout mycert.pem -out mycert.pem command.
Everything works well and the messages are sending OK. Please ignore the lack of error checking and excuse the total mess in the code, it’s just for learning purposes.
Can anyone point me what’s wrong? Also can you tell if I’m doing major mistakes, I’m trying to learn?(e.g. routine call order, major security problems, etc.)
Thanks!
According to the manual page (the example code at the bottom is very illustrative), the first call to
BIO_do_accept()will set up the accept BIO and does nothing else. Only the second and all subsequent calls will be actual calls to accept connections. This is quite illustrative of why OpenSSL will never win an award for «most intuitive API design».So what happens in your code? You only call
BIO_do_accept()within the loop. First time through the loop, it will set up the BIO and return immediately. Your code callsServlet()on the inexisting connection, andSSL_accept()fails, returning the error you are seeing. AfterServlet()returns, your code loops happily into the second invocation ofBIO_do_accept()which this time blocks, waiting for the first connection, and everything works as intended from here.To fix this, you need to call
BIO_do_accept()once before the loop, like this (using your broken style of error handling for consistency — you really need to fix your error handling!):