Securing Connections with SSL/TLS
A few illustrated reminds about simple static http server.
For complete explanations, please have a look at : https://web.enib.fr/~harrouet
SSL/TLS encryption
For the moment we exchange messages without any security. Any bad people sniffing the connection can know exchanged messages between the client and the server.
The solution is encrypting messages. To encrypt and decrypt a dialog, we need a key.
If the key is the same for both sides, it’s a symetric encryption .
The encryption is represented by a locked box.
If both guys own the key already, the problem is over, they can exchange messages in a safe way.
Now we consider we have to transmit the key.
The client asked for connection to the server.
We consider a locked box with 2 locks : one to open, one to close.
As a result we need 2 different keys to encrypt and decrypt the message.
- The Server puts his message in the box
- The Server closes the box with his private key ( encryption )
- The Server sends the message with a public key
The private key belongs to the server, nobody else can get it.
The client receives a public key with the message.
- The Client opens the box with the public key
- The response is encrypted with the same public key
- The Client sends the message
- The Server is the only one who can open the box since he’s got the private key ( related to the public key ).
There is still a problem.
If a bad guy relays those messages, he will decode everything. This is the man in the middle attack.
The Client needs to know the server identity .
The identity card is a certificate given by a trusted certification authority.
- The Server sends a certification request and his public key to the certfication authority.
- A signed certificate is produced
- and encrypted by the certification authority private key.
- Now the server joins this encrypted certificate to his message
- encrypts the message
3 sends the message
- At reception,
- the client decrypts the certificate with a certification authority public key ( this key is present alread in updated web browsers ).
the client verifies this certificate is update and related to the public key. - then the client and the server can exchange messages to construct a new key.
This new key permits exchanging messages with symetric encryption, which is quicker than asymetric one.
Once done, both guys can exchange in a safe way.
Static / Dynamic Server ( with websocket support) with SSL/TLS support
Producing Keys and Certificate
We consider we are the certification authority. We will produce a self-signed certificate, which will trigger a web browser warning.
# AUTHORITY CERTIFICATION PRIVATE KEY
$ openssl genrsa -aes256 -out ca.key 2048
# AUTO SIGNED CERTIFICATE REQUEST FOR AUTHORITY CERTIFICATION
$ openssl req -new -x509 -days 3600 -key ca.key -out ca.crt -subj '/C=FR/ST=Bretagne/L=Brest/O=ENIB/OU=RX/CN=raspberrypi.local'
# SERVER KEYS GENERATION
$ openssl genrsa -aes256 -out serverKey.pem 2048
# SERVER CERTIFICATE REQUEST
$ openssl req -new -out serverCert.pem -key serverKey.pem -subj '/C=FR/ST=Bretagne/L=Brest/O=ENIB/OU=RX/CN=raspberrypi.local'
# CERTIFICATE CREATION AND ENCRYPTION
$ openssl x509 -req -in serverCert.pem -CA ca.crt -CAkey ca.key -CAcreateserial -out serverCert.pem -days 1800
Static Server
Dynamic Server
httpDynServerWs.c
// CREDITS : F. Harrouet, www.enib.fr/~harrouet
//======================================================================
#include "main.h"
#include "httpServerLib.h"
#include "libWs.h"
#include <openssl/ssl.h>
#include <openssl/err.h>
#define BUFFER_SIZE 0x1000
void * dialogThread(void *arg)
{
char header[0x100];
char handshake[29];
pthread_detach(pthread_self());
char buffer[BUFFER_SIZE];
Request *req=(Request *)arg;
//---- receive and analyse HTTP request line by line ----
bool first=true;
for(;;)
{
if(req->ssl)
{
if(sslRecvLine(req->ssl,buffer,BUFFER_SIZE)<=0)
{ break; }
}
else
{
if(recvLine(req->sock,buffer,BUFFER_SIZE)<=0)
{ break; }
}
printf("BUFFER == %s",buffer);
if(first)
{
first=false;
sscanf(buffer,"%s %s",req->requestMethod,req->requestUri);
}
else if(sscanf(buffer,"Content-Length : %s",req->contentLength))
{
printf("CONTENTLENGTH\n");
// nothing more to be done
}
else if(!strcmp(buffer,"\n")||!strcmp(buffer,"\r\n"))
{
break; // end of header
}
else if(sscanf(buffer, "Upgrade : %31s", req->upgrade)==1)
{
// nothing more to be done
}
else if(sscanf(buffer, "Sec-WebSocket-Key : %31s", req->wsKey)==1)
{
// nothing more to be done
}
}
//------------------------------------------------------------------------------------------
//---- prepare and send HTTP reply ----
sprintf(req->fileName,"./www%s",req->requestUri);
char *params=strchr(req->fileName,'?');
if(params) { *params='\0'; }
struct stat st;
int r=stat(req->fileName,&st);
if((r!=-1)&&S_ISDIR(st.st_mode))
{
strcat(req->fileName,"/index.html");
r=stat(req->fileName,&st);
}
//------------------------------------------------------------------------------------------
//---- HANDLE UPGRADE TO WEBSOCKET REQUEST ----
if(!strcmp(req->requestMethod, "GET")&& !strcmp(req->requestUri, "/")&& !strcmp(req->upgrade, "websocket"))
{
printf("upgrading to websocket\n");
//---- prepare and send reply header ----
wsHandshake(handshake, req->wsKey);
const int hLen=snprintf(header, sizeof(header),
"HTTP/1.1 101 Switching Protocols\r\n"
"Connection: Upgrade\r\n"
"Upgrade: websocket\r\n"
"Sec-WebSocket-Accept: %s\r\n"
"\r\n",
handshake);
if(req->ssl) sslSendAll(req->ssl,header, hLen);
else sendAll(req->sock, header, hLen);
//---- receive some parameters from client ----
uint32_t params[3];
WsResult r;
if(req->ssl)
{ r = wssRecv(req->ssl, params, sizeof(params)); }
else
{ r = wsRecv(req->sock, params, sizeof(params)); }
printf("after reeive, paramas = %d %d \n", params[0], params[1]);
if((r.opcode==WS_BIN)&&(r.length!=sizeof(params)))
{
fprintf(stderr, "cannot receive parameters\n");
goto threadEnd;
}
const int delayMs=(int)ntohl(params[0]),
modulo=(int)ntohl(params[1]),
repeatCount=(int)ntohl(params[2]);
//---- produce and send values to client ----
printf("before for \n");
for(int repeat=0; repeat<repeatCount; ++repeat)
{
//---- produce new values ----
sleep(1);
uint32_t value=(uint32_t)(rand()%modulo);
uint32_t values[4]= {value, value+10, value*2, value*value}; // any value is suitable in this example
//---- send this value to client ----
for(int i=0; i<4; ++i)
{
values[i]=htonl(values[i]);
}
if(req->ssl)
{ wssSend(req->ssl, values, sizeof(values), WS_BIN); }
else
{ wsSend(req->sock, values, sizeof(values), WS_BIN); }
}
//---- signal end of websocket dialog ----
if(req->ssl)
{ wssSend(req->ssl, NULL, 0, WS_CLOSE); }
else
{ wsSend(req->sock, NULL, 0, WS_CLOSE); }
//---- websocket dialog stops here ----
goto threadEnd;
}
//------------------------------------------------------------------------------------------
// IF WE HAVE TO SERVE AN EXECUTABLE SCRIPT FILE ( CGI SCRIPT ) :
// rem : CGI scripts have to be placed in www/cgi/ folder
if((access(req->fileName,R_OK|X_OK)==0) && !strncmp(req->requestUri, "/cgi",4))
{
if(strcmp(req->requestMethod,"GET")&&strcmp(req->requestMethod,"POST"))
{
r=sprintf(buffer,"HTTP/1.0 400 Bad Request\n"
"Connection: close\n"
"Content-Type: text/html\n"
"\n"
"<html><body>\n"
"<b>400 - Bad Request</b><br>\n"
"method: %s<br>\n"
"uri: %s<br>\n"
"fileName: %s<br>\n"
"</body></html>\n",
req->requestMethod,
req->requestUri,
req->fileName);
if(req->ssl) sslSendAll(req->ssl,buffer,r);
else sendAll(req->sock,buffer,r);
}
else
{
// HTTP RESPONSE HEADER
r=sprintf(buffer,"HTTP/1.0 200 OK\n"
"Connection: close\n");
if(req->ssl) sslSendAll(req->ssl,buffer,r);
else sendAll(req->sock,buffer,r);
int localSock[2]={-1,-1};
if(req->ssl)
{
if(socketpair(PF_LOCAL,SOCK_STREAM,0,localSock)==-1)
{ perror("socketpair"); }
}
pid_t child=fork(); // execvp needs a new process, otherwhise the executed process will destroy all current threads.
switch(child)
{
case -1:
{
perror("fork");
break;
}
//############ CHILD CODE ######################################
case 0:
{
setenv("REQUEST_METHOD",req->requestMethod,1); // needed for queryProperties script
setenv("REQUEST_URI",req->requestUri,1); // needed for queryProperties script
setenv("CONTENT_LENGTH",req->contentLength,1); // needed for queryProperties script
if(req->ssl)
{
close(localSock[0]);
if(dup2(localSock[1],0)==-1)
{ perror("dup2"); exit(1); }
if(dup2(localSock[1],1)==-1)
{ perror("dup2"); exit(1); }
if(dup2(localSock[1],2)==-1)
{ perror("dup2"); exit(1); }
close(localSock[1]);
}
else
{
if(dup2(req->sock,0)==-1)
{ perror("dup2"); exit(1); }
if(dup2(req->sock,1)==-1)
{ perror("dup2"); exit(1); }
if(dup2(req->sock,2)==-1)
{ perror("dup2"); exit(1); }
close(req->sock);
}
char *args[2];
args[0]=req->fileName;
args[1]=(char *)0;
execvp(args[0],args);
perror("execvp");
exit(1);
}
//############ PARENT CODE #####################################
default:
{
if(req->ssl)
{
close(localSock[1]);
for(;;)
{
fd_set rdSet;
FD_ZERO(&rdSet);
FD_SET(req->sock,&rdSet);
FD_SET(localSock[0],&rdSet);
int maxFd=req->sock>localSock[0] ? req->sock : localSock[0];
struct timeval tv={0,0};
struct timeval *ptv=SSL_pending(req->ssl)
? &tv : (struct timeval *)0;
if(select(maxFd+1,&rdSet,(fd_set *)0,(fd_set *)0,ptv)==-1)
{ perror("select"); break; }
if(SSL_pending(req->ssl)||FD_ISSET(req->sock,&rdSet))
{
r=SSL_read(req->ssl,buffer,BUFFER_SIZE);
if(r<=0) break;
sendAll(localSock[0],buffer,r);
}
if(FD_ISSET(localSock[0],&rdSet))
{
r=recv(localSock[0],buffer,BUFFER_SIZE,0);
if(r<=0) break;
sslSendAll(req->ssl,buffer,r);
}
}
close(localSock[0]);
}
if(waitpid(child,(int *)0,0)==-1)
{ perror("waitpid"); }
}
}
}
}
//------------------------------------------------------------------------------------------
else // STATIC HTTP FILE SERVER
{
int input=open(req->fileName,O_RDONLY);
if(input==-1)
{
r=sprintf(buffer,"HTTP/1.0 404 Not Found\n"
"Connection: close\n"
"Content-Type: text/html\n"
"\n"
"<html><body>\n"
"<b>404 - Not Found</b><br>\n"
"method: %s<br>\n"
"uri: %s<br>\n"
"fileName: %s<br>\n"
"</body></html>\n",
req->requestMethod,
req->requestUri,
req->fileName);
if(req->ssl) sslSendAll(req->ssl,buffer,r);
else sendAll(req->sock,buffer,r);
}
else
{
const char *contentType="unknown/unknown";
const char *ext=strrchr(req->fileName,'.');
if(ext)
{
if(!strcmp(ext,".html")) contentType="text/html";
else if(!strcmp(ext,".png")) contentType="image/png";
else if(!strcmp(ext,".ico")) contentType="image/vnd.microsoft.icon";
}
r=sprintf(buffer,"HTTP/1.0 200 OK\n"
"Connection: close\n"
"Content-Type: %s\n"
"Content-Length: %ld\n\n",
contentType,
st.st_size);
if(req->ssl) sslSendAll(req->ssl,buffer,r);
else sendAll(req->sock,buffer,r);
for(;;)
{
r=read(input,buffer,BUFFER_SIZE);
if(r<=0) break;
if(req->ssl) sslSendAll(req->ssl,buffer,r);
else sendAll(req->sock,buffer,r);
}
close(input);
}
}
threadEnd:
destroyRequest(req);
return (void *)0;
}
//======================================================================
// MAIN
//======================================================================
int main(int argc, char **argv)
{
//---- avoid exiting on broken client connection (ignore SIGPIPE) ----
struct sigaction sa;
memset(&sa,0,sizeof(struct sigaction));
sa.sa_handler=SIG_IGN;
if(sigaction(SIGPIPE,&sa,(struct sigaction *)0)==-1)
{ perror("sigaction"); exit(1); }
//---- check command line arguments ----
if(argc!=3)
{ fprintf(stderr,"usage: %s http_port https_port\n",argv[0]); exit(1); }
//---- extract local port number ----
int httpPortNumber,httpsPortNumber;
if(sscanf(argv[1],"%d",&httpPortNumber)!=1)
{ fprintf(stderr,"invalid port %s\n",argv[1]); exit(1); }
if(sscanf(argv[2],"%d",&httpsPortNumber)!=1)
{ fprintf(stderr,"invalid port %s\n",argv[2]); exit(1); }
//---- multithreaded TCP server launching dialogThread() ----
//---- create HTTP listen socket ----
int httpSocket=socket(PF_INET,SOCK_STREAM,0);
if(httpSocket==-1)
{ perror("socket"); exit(1); }
// ... avoiding timewait problems (optional)
int on=1;
if(setsockopt(httpSocket,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(int))==-1)
{ perror("setsockopt"); exit(1); }
// ... bound to any local address on the specified port
struct sockaddr_in myAddr;
myAddr.sin_family=AF_INET;
myAddr.sin_port=htons(httpPortNumber);
myAddr.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(httpSocket,(struct sockaddr *)&myAddr,sizeof(myAddr))==-1)
{ perror("bind"); exit(1); }
// ... listening connections
if(listen(httpSocket,10)==-1)
{ perror("listen"); exit(1); }
//---- create HTTPS listen socket ----
int httpsSocket=socket(PF_INET,SOCK_STREAM,0);
if(httpsSocket==-1)
{ perror("socket"); exit(1); }
// ... avoiding timewait problems (optional)
on=1;
if(setsockopt(httpsSocket,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(int))==-1)
{ perror("setsockopt"); exit(1); }
// ... bound to any local address on the specified port
myAddr.sin_family=AF_INET;
myAddr.sin_port=htons(httpsPortNumber);
myAddr.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(httpsSocket,(struct sockaddr *)&myAddr,sizeof(myAddr))==-1)
{ perror("bind"); exit(1); }
// ... listening connections
if(listen(httpsSocket,10)==-1)
{ perror("listen"); exit(1); }
for(;;)
{
//---- wait for connections ----
fd_set rdSet;
FD_ZERO(&rdSet);
FD_SET(httpSocket,&rdSet);
FD_SET(httpsSocket,&rdSet);
int maxFd=httpSocket>httpsSocket ? httpSocket : httpsSocket;
if(select(maxFd+1,&rdSet,(fd_set *)0,(fd_set *)0,(struct timeval *)0)==-1)
{ perror("select"); exit(1); }
//---- accept new HTTP connection (if available)----
if(FD_ISSET(httpSocket,&rdSet))
{
struct sockaddr_in fromAddr;
socklen_t len=sizeof(fromAddr);
int dialogSocket=accept(httpSocket,(struct sockaddr *)&fromAddr,&len);
if(dialogSocket==-1)
{ perror("accept"); exit(1); }
printf("new HTTP connection from %s:%d\n",
inet_ntoa(fromAddr.sin_addr),ntohs(fromAddr.sin_port));
//---- start a new dialog thread ----
pthread_t th;
if(pthread_create(&th,(pthread_attr_t *)0,
dialogThread,createRequest(dialogSocket,false)))
{ fprintf(stderr,"cannot create thread\n"); exit(1); }
}
//---- accept new HTTPS connection (if available)----
if(FD_ISSET(httpsSocket,&rdSet))
{
struct sockaddr_in fromAddr;
socklen_t len=sizeof(fromAddr);
int dialogSocket=accept(httpsSocket,(struct sockaddr *)&fromAddr,&len);
if(dialogSocket==-1)
{ perror("accept"); exit(1); }
printf("new HTTPS connection from %s:%d\n",
inet_ntoa(fromAddr.sin_addr),ntohs(fromAddr.sin_port));
//---- start a new dialog thread ----
pthread_t th;
if(pthread_create(&th,(pthread_attr_t *)0,
dialogThread,createRequest(dialogSocket,true)))
{ fprintf(stderr,"cannot create thread\n"); exit(1); }
}
}
//---- close listen sockets ----
close(httpSocket);
close(httpsSocket);
return 0;
}
//^^^^^^^^^^^^^^^^^^^^^^^^^^ EOF ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# RPI Terminal
./exec_httpDynServerWs 5555 8123
new HTTPS connection from 192.168.0.1:41324
Enter PEM pass phrase:
new HTTP connection from 192.168.0.1:57792
new HTTPS connection from 192.168.0.1:41326
new HTTPS connection from 192.168.0.1:41332
BUFFER == GET / HTTP/1.1
BUFFER == Host: 192.168.0.2:8123
BUFFER == Connection: keep-alive
BUFFER == Cache-Control: max-age=0
BUFFER == Upgrade-Insecure-Requests: 1
BUFFER == User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/71.0.3578.98 Chrome/71.0.3578.98 Safari/537.36
BUFFER == Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
BUFFER == Accept-Encoding: gzip, deflate, br
BUFFER == Accept-Language: en-US,en;q=0.9
BUFFER ==
new HTTPS connection from 192.168.0.1:41334
new HTTPS connection from 192.168.0.1:41336
BUFFER == GET /favicon.ico HTTP/1.1
BUFFER == Host: 192.168.0.2:8123
BUFFER == Connection: keep-alive
BUFFER == Pragma: no-cache
BUFFER == Cache-Control: no-cache
BUFFER == User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/71.0.3578.98 Chrome/71.0.3578.98 Safari/537.36
BUFFER == Accept: image/webp,image/apng,image/*,*/*;q=0.8
BUFFER == Referer: https://192.168.0.2:8123/
BUFFER == Accept-Encoding: gzip, deflate, br
BUFFER == Accept-Language: en-US,en;q=0.9
BUFFER ==
new HTTPS connection from 192.168.0.1:41340
new HTTPS connection from 192.168.0.1:41342
new HTTPS connection from 192.168.0.1:41344
BUFFER == GET /page.html HTTP/1.1
BUFFER == Host: 192.168.0.2:8123
BUFFER == Connection: keep-alive
BUFFER == Upgrade-Insecure-Requests: 1
BUFFER == User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/71.0.3578.98 Chrome/71.0.3578.98 Safari/537.36
BUFFER == Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
BUFFER == Referer: https://192.168.0.2:8123/
BUFFER == Accept-Encoding: gzip, deflate, br
BUFFER == Accept-Language: en-US,en;q=0.9
BUFFER ==