326
GnuTLS Pre-Shared Key Client-Server Example Code
It is very easy to set up a secure TLS connection with pre-shared key authentication using GnuTLS. The following code is an example of how to do it. The code is heavily documented, so it should be readable even to someone who has never worked with GnuTLS before. The code is in the public domain, so feel free to do absolutely anything you want with it.
Here is the GnuTLS Manual, which is a great reference.
Makefile
1 # Makefile for compiling the GnuTLS pre-shared key client-server example.
2
3 server: server.c
4 gcc -Wall -Werror -c server.c -o server.o `pkg-config gnutls --cflags`
5 gcc server.o -o server `pkg-config gnutls --libs`
6
7 client: client.c
8 gcc -Wall -Werror -c client.c -o client.o `pkg-config gnutls --cflags`
9 gcc client.o -o client `pkg-config gnutls --libs`
10
Server
1 // GnuTLS pre-shared key (PSK) client-server example.
2 // This is the SERVER (server.c).
3 //
4 // This code was written by Taylor Hornby on April 18, 2013. You can find it
5 // on the web at:
6 // https://defuse.ca/gnutls-psk-client-server-example.htm
7 //
8 // This code is in the public domain. You can do whatever you want with it.
9 //
10 // In this simple example,
11 // 1. The server accepts a TCP connection from the client.
12 // 2. The server and client perform the SSL/TLS handshake over the connection.
13 // 3. The server sends the client some data.
14 // 4. The server and client close the SSL/TLS and TCP connection.
15 // 5. The server and the client exit.
16 //
17 // The code is heavily commented so that it can be understood by someone who has
18 // never used GnuTLS before. The GnuTLS manual can be found here:
19 // http://www.gnutls.org/manual/gnutls.html
20
21 // Standard Library stuff
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <errno.h>
26
27 // Stuff for sockets.
28 #include <unistd.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
31 #include <sys/types.h>
32 #include <sys/socket.h>
33
34 // GnuTLS!
35 #include <gnutls/gnutls.h>
36
37 // This is the secret key that must be shared (over a secure channel!) between
38 // the client and server. Obviously in a real application it should be in
39 // a configuration file or something and not built-in constant. It also
40 // shouldn't be an ASCII string. Use a good CSPRNG!
41 #define SECRET_KEY "THIS IS THE PRE-SHARED KEY."
42 // This is the port number that the server will listen on.
43 #define PORT 8050
44 // GnuTLS log level. 9 is the most verbose.
45 #define LOG_LEVEL 0
46
47 int accept_one_connection(int port);
48 void error_exit(const char *msg);
49 // GnuTLS callbacks.
50 int psk_creds(gnutls_session_t, const char*, gnutls_datum_t*);
51 ssize_t data_push(gnutls_transport_ptr_t, const void*, size_t);
52 ssize_t data_pull(gnutls_transport_ptr_t, void*, size_t);
53 void print_logs(int, const char*);
54 void print_audit_logs(gnutls_session_t, const char*);
55
56 int main(int argc, char **argv)
57 {
58 // We use the 'res' variable to capture return values, so we can check for
59 // success/failure.
60 int res;
61
62 // Initialize GnuTLS's global state. You only have to do this once.
63 gnutls_global_init();
64
65 // Enable logging (for debugging).
66 // Be careful with this in a real application. You don't want to reveal the
67 // logging information to any potential attackers.
68 gnutls_global_set_log_level(LOG_LEVEL);
69 gnutls_global_set_log_function(print_logs);
70 // Enable logging (for auditing).
71 // (commented out because it's not available in my version of GnuTLS)
72 // gnutls_global_set_audit_log_function(print_audit_logs);
73
74 // A GnuTLS session is like a socket for an SSL/TLS connection.
75 gnutls_session_t session;
76
77 // Initialize the session for our connection from the client.
78 res = gnutls_init(&session, GNUTLS_SERVER);
79 if (res != GNUTLS_E_SUCCESS) {
80 error_exit("gnutls_init() failed.\n");
81 }
82
83 // Allocate the structure we use to tell GnuTLS what the credentials
84 // (pre-shared key) are.
85 gnutls_psk_server_credentials_t cred;
86 res = gnutls_psk_allocate_server_credentials(&cred);
87 if (res != 0) {
88 error_exit("gnutls_psk_allocate_server_credentials() failed.\n");
89 }
90 // GnuTLS will call psk_creds to ask for the key associated with the
91 // client's username.
92 gnutls_psk_set_server_credentials_function(cred, psk_creds);
93 // Pass the "credentials" to the GnuTLS session. GnuTLS does NOT make an
94 // internal copy of the information, so we have to keep the 'cred' structure
95 // in memory (and not modify it) until we're done with this session.
96 res = gnutls_credentials_set(session, GNUTLS_CRD_PSK, cred);
97 if (res != 0) {
98 error_exit("gnutls_credentials_set() failed.\n");
99 }
100
101 // Set the cipher suite priorities.
102 // See section 6.10 of the GnuTLS manuals for a description of this string.
103 // Run `gnutls-cli -l` to see what your GnuTLS supports.
104 const char *error = NULL;
105 // Here we allow all 128+ bit ciphers except RC4 and disable SSL3 and TLS1.0.
106 res = gnutls_priority_set_direct(
107 session,
108 "SECURE128:-VERS-SSL3.0:-VERS-TLS1.0:-ARCFOUR-128:+PSK:+DHE-PSK",
109 &error
110 );
111 if (res != GNUTLS_E_SUCCESS) {
112 error_exit("gnutls_priority_set_direct() failed.\n");
113 }
114
115 // Accept a TCP connection.
116 int connfd = accept_one_connection(PORT);
117
118 // Below we give GnuTLS access to the transport layer. GnuTLS needs a way of
119 // reading and writing to and from the TCP socket (or whatever transport
120 // layer you're using). Newer versions of GnuTLS can work directly with
121 // Berkely sockets (see gnutls_transport_set_int()), but here we use the
122 // push/pull callbacks as an example.
123
124 // GnuTLS passes a pointer to the send and receive callbacks. Here we
125 // construct a pointer to the connection's file descriptor which we will
126 // tell GnuTLS to pass to the callbacks.
127 int *connfdPtr = malloc(sizeof(int));
128 *connfdPtr = connfd;
129 gnutls_transport_set_ptr(session, connfdPtr);
130
131 // Set the callback that allows GnuTLS to PUSH data TO the transport layer
132 gnutls_transport_set_push_function(session, data_push);
133 // Set the callback that allows GnuTls to PULL data FROM the tranport layer
134 gnutls_transport_set_pull_function(session, data_pull);
135
136 // Now we perform the actual SSL/TLS handshake.
137 // If you wanted to, you could send some data over the tcp socket before
138 // giving it to GnuTLS and performing the handshake. See the GnuTLS manual
139 // on STARTTLS for more information.
140 do {
141 res = gnutls_handshake(session);
142 // GnuTLS manual says to keep trying until it returns zero (success) or
143 // encounters a fatal error.
144 } while ( res != 0 && !gnutls_error_is_fatal(res) );
145 // If there is a fatal error, it is a fatal error for the PROTOCOL, not
146 // a fatal error for the GnuTLS library. In this example, we just exit. In
147 // a real application, you would alert the user, try again, etc.
148 if (gnutls_error_is_fatal(res)) {
149 error_exit("Fatal error during handshake.\n");
150 }
151
152 // If all went well, we've established a secure connection to the client.
153 // We can now send some data.
154 int i;
155 char buf[100];
156 for (i = 1; i <= 10; i++) {
157 sprintf(buf, "Server %d\r\n", i);
158 do {
159 // gnutls_record_send() behaves like send(), so it doesn't always
160 // send all of the available data. You should check the return value
161 // and send anything it didn't send (just like you would with
162 // send()).
163 res = gnutls_record_send(session, buf, strlen(buf));
164 } while (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN);
165 if (gnutls_error_is_fatal(res)) {
166 // Again, a fatal error doesn't mean you have to exit, it's just
167 // a fatal error for the protocol. You should alert the user, retry,
168 // etc.
169 error_exit("Fatal error during send.\n");
170 }
171 }
172
173 // Tear down the SSL/TLS connection. You could just close the TCP socket,
174 // but this authenticates to the client your intent to close the connection,
175 // so they can distinguish between you actually wanting to close the
176 // connection and an attacker forcing it to close. Always do this.
177 gnutls_bye(session, GNUTLS_SHUT_RDWR);
178
179 // Destroy the session.
180 gnutls_deinit(session);
181
182 // Close the TCP connection.
183 close(connfd);
184 // GnuTLS shouldn't be making calls to the push/pull functions, so it's now
185 // safe to deallocate the pointer to the socket.
186 free(connfdPtr);
187
188 // Destroy the structure we used to tell GnuTLS what credentials to use.
189 // We have to do this after we call gnutls_deinit(), since GnuTLS doesn't
190 // make an internal copy of the structure.
191 gnutls_psk_free_server_credentials(cred);
192
193 // Finally, tear down GnuTLS's global state.
194 gnutls_global_deinit();
195
196 printf("All done!\n");
197 return 0;
198 }
199
200 // GnuTLS calls this function to get the pre-shared key. The client will tell
201 // the server its username, and GnuTLS will give us that username. We have to
202 // return the key that we share with that client. We set this callback with
203 // gnutls_psk_set_server_credentials_function().
204 int psk_creds(gnutls_session_t session, const char *username, gnutls_datum_t *key)
205 {
206 // For this example, we ignore the username and return the same key every
207 // time. In a real application, you would look up the key for the username
208 // and return that. If the username does not exist, return a negative
209 // number (see the manual).
210 key->size = strlen(SECRET_KEY);
211 key->data = gnutls_malloc(key->size);
212 if (key->data == NULL) {
213 return -1;
214 }
215 memcpy(key->data, SECRET_KEY, key->size);
216 return 0;
217 }
218
219 // GnuTLS calls this function to send data through the transport layer. We set
220 // this callback with gnutls_transport_set_push_function(). It should behave
221 // like send() (see the manual for specifics).
222 ssize_t data_push(gnutls_transport_ptr_t ptr, const void* data, size_t len)
223 {
224 int sockfd = *(int*)(ptr);
225 return send(sockfd, data, len, 0);
226 }
227
228 // GnuTLS calls this function to receive data from the transport layer. We set
229 // this callback with gnutls_transport_set_pull_function(). It should act like
230 // recv() (see the manual for specifics).
231 ssize_t data_pull(gnutls_transport_ptr_t ptr, void* data, size_t maxlen)
232 {
233 int sockfd = *(int*)(ptr);
234 return recv(sockfd, data, maxlen, 0);
235 }
236
237 // GnuTLS will call this function whenever there is a new debugging log message.
238 void print_logs(int level, const char* msg)
239 {
240 printf("GnuTLS [%d]: %s", level, msg);
241 }
242
243 // GnuTLS will call this function whenever there is a new audit log message.
244 void print_audit_logs(gnutls_session_t session, const char* message)
245 {
246 printf("GnuTLS Audit: %s", message);
247 }
248
249 // Listens on 'port' for a TCP connection. Accepts at most one connection.
250 int accept_one_connection(int port)
251 {
252 int res;
253 // Listen for a TCP connection.
254 struct sockaddr_in serv_addr;
255 int listenfd = socket(AF_INET, SOCK_STREAM, 0);
256 if (listenfd < 0) {
257 error_exit("socket() failed.\n");
258 }
259 int yes = 1;
260 if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
261 error_exit("setsockopt() failed.\n");
262 }
263 memset(&serv_addr, 0, sizeof(serv_addr));
264 serv_addr.sin_family = AF_INET;
265 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
266 serv_addr.sin_port = htons(port);
267 res = bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
268 if (res < 0) {
269 error_exit("bind() failed.\n");
270 }
271 res = listen(listenfd, 10);
272 if (res < 0) {
273 error_exit("listen() failed.\n");
274 }
275
276 printf("Waiting for a connection...\n");
277
278 // Accept a TCP connection.
279 int connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
280 if (connfd < 0) {
281 error_exit("accept() failed.\n");
282 }
283
284 printf("A client connected!\n");
285
286 close(listenfd);
287
288 return connfd;
289 }
290
291 void error_exit(const char *msg)
292 {
293 printf("ERROR: %s", msg);
294 exit(1);
295 }
Client
1 // GnuTLS pre-shared key (PSK) client-server example.
2 // This is the CLIENT (client.c).
3 //
4 // This code was written by Taylor Hornby on April 18, 2013. You can find it
5 // on the web at:
6 // https://defuse.ca/gnutls-psk-client-server-example.htm
7 //
8 // This code is in the public domain. You can do whatever you want with it.
9 //
10 // In this simple example,
11 // 1. The server accepts a TCP connection from the client.
12 // 2. The server and client perform the SSL/TLS handshake over the connection.
13 // 3. The server sends the client some data.
14 // 4. The server and client close the SSL/TLS and TCP connection.
15 // 5. The server and the client exit.
16 //
17 // The code is heavily commented so that it can be understood by someone who has
18 // never used GnuTLS before. The GnuTLS manual can be found here:
19 // http://www.gnutls.org/manual/gnutls.html
20
21 // Standard library stuff
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <errno.h>
26
27 // Stuff for sockets
28 #include <unistd.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
31 #include <sys/types.h>
32 #include <sys/socket.h>
33
34 // GnuTLS!
35 #include <gnutls/gnutls.h>
36
37 // This is the secret key that must be shared (over a secure channel!) between
38 // the client and server. Obviously in a real application it should be in
39 // a configuration file or something and not built-in constant. It also
40 // shouldn't be an ASCII string. Use a good CSPRNG!
41 #define SECRET_KEY "THIS IS THE PRE-SHARED KEY."
42
43 // IPv4 address of the server which we will connect to.
44 #define SERVER_IP "127.0.0.1"
45 // The TCP port number that the server is running on, which we will connect to.
46 #define SERVER_PORT 8050
47 // GnuTLS log level. 9 is the most verbose.
48 #define LOG_LEVEL 0
49
50 int make_one_connection(const char *address, int port);
51 void error_exit(const char *msg);
52 // GnuTLS callbacks.
53 int psk_creds(gnutls_session_t, const char*, gnutls_datum_t*);
54 ssize_t data_push(gnutls_transport_ptr_t, const void*, size_t);
55 ssize_t data_pull(gnutls_transport_ptr_t, void*, size_t);
56 void print_logs(int, const char*);
57 void print_audit_logs(gnutls_session_t, const char*);
58
59 int main(int argc, char **argv)
60 {
61 // We use the 'res' variable to capture return values, so we can check for
62 // success/failure.
63 int res;
64
65 // Initialize GnuTLS's global state. You only have to do this once.
66 gnutls_global_init();
67
68 // Enable logging (for debugging).
69 // Be careful with this in a real application. You don't want to reveal the
70 // logging information to any potential attackers.
71 gnutls_global_set_log_level(LOG_LEVEL);
72 gnutls_global_set_log_function(print_logs);
73 // Enable logging (for auditing).
74 // (commented out because it's not available in my version of GnuTLS)
75 // gnutls_global_set_audit_log_function(print_audit_logs);
76
77 // A GnuTLS session is like a socket for an SSL/TLS connection.
78 gnutls_session_t session;
79
80 // Initialize the session for our connection to the server.
81 res = gnutls_init(&session, GNUTLS_CLIENT);
82 if (res != GNUTLS_E_SUCCESS) {
83 error_exit("gnutls_init() failed.\n");
84 }
85
86 // Allocate a structure that we use to tell GnuTLS what our "username" and
87 // pre-shared key is.
88 gnutls_psk_client_credentials_t cred;
89 res = gnutls_psk_allocate_client_credentials(&cred);
90 if (res != 0) {
91 error_exit("gnutls_psk_allocate_client_credentials() failed.\n");
92 }
93 // Construct the pre-shared key in GnuTLS's 'datum' structure, whose
94 // definition is as follows:
95 // typedef struct {
96 // unsigned char *data;
97 // unsigned int size;
98 // } gnutls_datum_t;
99 gnutls_datum_t key;
100 key.size = strlen(SECRET_KEY);
101 key.data = malloc(key.size);
102 memcpy(key.data, SECRET_KEY, key.size);
103 // Put the username and key into the structure we use to tell GnuTLs what
104 // the credentials are. The example server doesn't care about usernames, so
105 // we use "Alice" here.
106 res = gnutls_psk_set_client_credentials(cred, "Alice", &key, GNUTLS_PSK_KEY_RAW);
107 memset(key.data, 0, key.size);
108 free(key.data);
109 key.data = NULL;
110 key.size = 0;
111 // You could instead use a callback to give the credentials to GnuTLS. See
112 // gnutls_psk_set_client_credentials_function().
113 if (res != 0) {
114 error_exit("gnutls_psk_set_client_credentials() failed.\n");
115 }
116 // Pass our credentials (which contains the username and key) to GnuTLS.
117 res = gnutls_credentials_set(session, GNUTLS_CRD_PSK, cred);
118 if (res != 0) {
119 error_exit("gnutls_credentials_set() failed.\n");
120 }
121
122 // Set the cipher suite priorities.
123 // See section 6.10 of the GnuTLS manuals for a description of this string.
124 // Run `gnutls-cli -l` to see what your GnuTLS supports.
125 const char *error = NULL;
126 // Here we allow all 128+ bit ciphers except RC4 and disable SSL3 and TLS1.0.
127 res = gnutls_priority_set_direct(
128 session,
129 "SECURE128:-VERS-SSL3.0:-VERS-TLS1.0:-ARCFOUR-128:+PSK:+DHE-PSK",
130 &error
131 );
132 if (res != GNUTLS_E_SUCCESS) {
133 error_exit("gnutls_priority_set_direct() failed.\n");
134 }
135
136 // Make a TCP connection to the server.
137 int connfd = make_one_connection(SERVER_IP, SERVER_PORT);
138
139 // Below we give GnuTLS access to the transport layer. GnuTLS needs a way of
140 // reading and writing to and from the TCP socket (or whatever transport
141 // layer you're using). Newer versions of GnuTLS can work directly with
142 // Berkely sockets (see gnutls_transport_set_int()), but here we use the
143 // push/pull callbacks as an example.
144
145 // GnuTLS passes a pointer to the send and receive callbacks. Here we
146 // construct a pointer to the connection's file descriptor which we will
147 // tell GnuTLS to pass to the callbacks.
148 int *connfdPtr = malloc(sizeof(int));
149 *connfdPtr = connfd;
150 gnutls_transport_set_ptr(session, connfdPtr);
151
152 // Set the callback that allows GnuTLS to PUSH data TO the transport layer
153 gnutls_transport_set_push_function(session, data_push);
154 // Set the callback that allows GnuTls to PULL data FROM the tranport layer
155 gnutls_transport_set_pull_function(session, data_pull);
156
157 // Now we perform the actual SSL/TLS handshake.
158 // If you wanted to, you could send some data over the tcp socket before
159 // giving it to GnuTLS and performing the handshake. See the GnuTLS manual
160 // on STARTTLS for more information.
161 do {
162 res = gnutls_handshake(session);
163 } while ( res != 0 && !gnutls_error_is_fatal(res) );
164
165 if (gnutls_error_is_fatal(res)) {
166 error_exit("Fatal error during handshake.\n");
167 }
168
169 // If the handshake worked, we can now receive the data that the server is
170 // sending to us.
171 printf("------- BEGIN DATA FROM SERVER -------\n");
172 char buf[100];
173 res = gnutls_record_recv(session, buf, sizeof(buf));
174 while (res != 0) {
175 if (res == GNUTLS_E_REHANDSHAKE) {
176 error_exit("Peer wants to re-handshake but we don't support that.\n");
177 } else if (gnutls_error_is_fatal(res)) {
178 error_exit("Fatal error during read.\n");
179 } else if (res > 0) {
180 fwrite(buf, 1, res, stdout);
181 fflush(stdout);
182 }
183 res = gnutls_record_recv(session, buf, sizeof(buf));
184 }
185 printf("------- END DATA FROM SERVER -------\n");
186
187 // Tear down the SSL/TLS connection. You could just close the TCP socket,
188 // but this authenticates to the client your intent to close the connection,
189 // so they can distinguish between you actually wanting to close the
190 // connection and an attacker forcing it to close. Always do this.
191 gnutls_bye(session, GNUTLS_SHUT_RDWR);
192
193 // Destroy the session.
194 gnutls_deinit(session);
195
196 // Close the TCP connection.
197 close(connfd);
198 // GnuTLS shouldn't be making calls to the push/pull functions, so it's now
199 // safe to deallocate the pointer to the socket.
200 free(connfdPtr);
201
202 // Destroy the structure we used to tell GnuTLS what credentials to use.
203 // We have to do this after we call gnutls_deinit(), since GnuTLS doesn't
204 // make an internal copy of the structure.
205 gnutls_psk_free_client_credentials(cred);
206
207 // Finally, tear down the GnuTLS's global state.
208 gnutls_global_deinit();
209
210 printf("All done!\n");
211 return 0;
212 }
213
214 // GnuTLS calls this function to send data through the transport layer. We set
215 // this callback with gnutls_transport_set_push_function(). It should behave
216 // like send() (see the manual for specifics).
217 ssize_t data_push(gnutls_transport_ptr_t ptr, const void* data, size_t len)
218 {
219 int sockfd = *(int*)(ptr);
220 return send(sockfd, data, len, 0);
221 }
222
223 // GnuTLS calls this function to receive data from the transport layer. We set
224 // this callback with gnutls_transport_set_pull_function(). It should act like
225 // recv() (see the manual for specifics).
226 ssize_t data_pull(gnutls_transport_ptr_t ptr, void* data, size_t maxlen)
227 {
228 int sockfd = *(int*)(ptr);
229 return recv(sockfd, data, maxlen, 0);
230 }
231
232 // GnuTLS will call this function whenever there is a new debugging log message.
233 void print_logs(int level, const char* msg)
234 {
235 printf("GnuTLS [%d]: %s", level, msg);
236 }
237
238 // GnuTLS will call this function whenever there is a new audit log message.
239 void print_audit_logs(gnutls_session_t session, const char* message)
240 {
241 printf("GnuTLS Audit: %s", message);
242 }
243
244 // Makes a TCP connection to the given IPv4 address and port number.
245 int make_one_connection(const char *address, int port)
246 {
247 int res;
248 int connfd = socket(AF_INET, SOCK_STREAM, 0);
249 struct sockaddr_in serv_addr;
250 if (connfd < 0) {
251 error_exit("socket() failed.\n");
252 }
253 memset(&serv_addr, 0, sizeof(serv_addr));
254 serv_addr.sin_family = AF_INET;
255 serv_addr.sin_port = htons(port);
256 res = inet_pton(AF_INET, address, &serv_addr.sin_addr);
257 if (res != 1) {
258 error_exit("inet_pton() failed.\n");
259 }
260 res = connect(connfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
261 if (res < 0) {
262 error_exit("connect() failed.\n");
263 }
264 return connfd;
265 }
266
267 void error_exit(const char *msg)
268 {
269 printf("ERROR: %s", msg);
270 exit(1);
271 }