d7f4a2cfac54e10158a172662eecaa7550899a31
[vlc.git] / test / modules / misc / tls.c
1 /*****************************************************************************
2  * tls.c: Transport Layer Security module test
3  *****************************************************************************
4  * Copyright © 2016 Rémi Denis-Courmont
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #undef NDEBUG
26 #include <assert.h>
27 #include <errno.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <poll.h>
34 #include <fcntl.h>
35 #include <unistd.h>
36
37 #include <vlc_common.h>
38 #include <vlc_modules.h>
39 #include <vlc_tls.h>
40 #include <vlc_dialog.h>
41 #include "../../../lib/libvlc_internal.h"
42
43 #include <vlc/vlc.h>
44
45 static int tlspair(int fds[2])
46 {
47     return vlc_socketpair(PF_LOCAL, SOCK_STREAM, 0, fds, true);
48 }
49
50 static int question_callback(vlc_object_t *obj, const char *varname,
51                              vlc_value_t old, vlc_value_t cur, void *data)
52 {
53     dialog_question_t *q = cur.p_address;
54     int *value = data;
55
56     q->answer = *value;
57
58     assert(obj == VLC_OBJECT(obj->p_libvlc));
59     assert(!strcmp(varname, "dialog-question"));
60     (void) old;
61     return VLC_SUCCESS;
62 }
63
64 static libvlc_instance_t *vlc;
65 static vlc_object_t *obj;
66 static vlc_tls_creds_t *server_creds;
67 static vlc_tls_creds_t *client_creds;
68
69 static void *tls_echo(void *data)
70 {
71     vlc_tls_t *tls = data;
72     struct pollfd ufd;
73     ssize_t val;
74     char buf[256];
75
76     ufd.fd = vlc_tls_GetFD(tls);
77
78     while ((val = vlc_tls_SessionHandshake(server_creds, tls)) > 0)
79     {
80         switch (val)
81         {
82             case 1:  ufd.events = POLLIN;  break;
83             case 2:  ufd.events = POLLOUT; break;
84             default: vlc_assert_unreachable();
85         }
86         poll(&ufd, 1, -1);
87     }
88
89     if (val < 0)
90         goto error;
91
92     while ((val = vlc_tls_Read(tls, buf, sizeof (buf), false)) > 0)
93         if (vlc_tls_Write(tls, buf, val) < val)
94             goto error;
95
96     if (val < 0 || vlc_tls_Shutdown(tls, false))
97         goto error;
98
99     vlc_tls_Close(tls);
100     return tls;
101 error:
102     vlc_tls_Close(tls);
103     return NULL;
104 }
105
106 static int securepair(vlc_thread_t *th, vlc_tls_t **restrict client,
107                       const char *const *alpnv[2], char **restrict alp)
108 {
109     vlc_tls_t *server;
110     int val;
111     int insecurev[2];
112
113     val = tlspair(insecurev);
114     assert(val == 0);
115
116     server = vlc_tls_ServerSessionCreate(server_creds, insecurev[0], alpnv[0]);
117     assert(server != NULL);
118
119     val = vlc_clone(th, tls_echo, server, VLC_THREAD_PRIORITY_LOW);
120     assert(val == 0);
121
122     *client = vlc_tls_ClientSessionCreateFD(client_creds, insecurev[1],
123                                           "localhost", "vlc-tls-test",
124                                           alpnv[1], alp);
125     if (*client == NULL)
126     {
127         val = close(insecurev[1]);
128         assert(val == 0);
129         vlc_join(*th, NULL);
130         return -1;
131     }
132     return 0;
133 }
134
135 static const char certpath[] = SRCDIR"/modules/misc/certkey.pem";
136 static const char *const alpn[] = { "foo", "bar", NULL };
137
138 int main(void)
139 {
140     int val;
141     int answer = 0;
142
143     /* Create fake home for stored keys */
144     char homedir[] = "/tmp/vlc-test-XXXXXX";
145     if (mkdtemp(homedir) != homedir)
146     {
147         perror("Temporary directory");
148         return 77;
149     }
150
151     assert(!strncmp(homedir, "/tmp/vlc-test-", 14));
152     setenv("HOME", homedir, 1);
153     setenv("VLC_PLUGIN_PATH", "../modules", 1);
154
155     vlc = libvlc_new(0, NULL);
156     assert(vlc != NULL);
157     obj = VLC_OBJECT(vlc->p_libvlc_int);
158
159     server_creds = vlc_tls_ServerCreate(obj, SRCDIR"/nonexistent", NULL);
160     assert(server_creds == NULL);
161     server_creds = vlc_tls_ServerCreate(obj, SRCDIR"/samples/empty.voc", NULL);
162     assert(server_creds == NULL);
163     server_creds = vlc_tls_ServerCreate(obj, certpath, SRCDIR"/nonexistent");
164     assert(server_creds == NULL);
165     server_creds = vlc_tls_ServerCreate(obj, certpath, NULL);
166     if (server_creds == NULL)
167     {
168         libvlc_release(vlc);
169         return 77;
170     }
171
172     client_creds = vlc_tls_ClientCreate(obj);
173     assert(client_creds != NULL);
174
175     var_Create(obj, "dialog-question", VLC_VAR_ADDRESS);
176     var_AddCallback(obj, "dialog-question", question_callback, &answer);
177     dialog_Register(obj);
178
179     vlc_thread_t th;
180     vlc_tls_t *tls;
181     const char *const *alpnv[2] = { alpn + 1, alpn };
182     char *alp;
183     void *p;
184
185     /* Test unknown certificate */
186     answer = 0;
187     val = securepair(&th, &tls, alpnv, &alp);
188     assert(val == -1);
189
190     /* Accept unknown certificate */
191     answer = 2;
192     val = securepair(&th, &tls, alpnv, &alp);
193     assert(val == 0);
194     assert(alp != NULL);
195     assert(!strcmp(alp, "bar"));
196     free(alp);
197
198     /* Do some I/O */
199     char buf[12];
200     struct iovec iov;
201
202     iov.iov_base = buf;
203     iov.iov_len = sizeof (buf);
204     val = tls->readv(tls, &iov, 1);
205     assert(val == -1 && errno == EAGAIN);
206
207     val = vlc_tls_Write(tls, "Hello ", 6);
208     assert(val == 6);
209     val = vlc_tls_Write(tls, "world!", 6);
210     assert(val == 6);
211
212     val = vlc_tls_Read(tls, buf, sizeof (buf), true);
213     assert(val == 12);
214     assert(!memcmp(buf, "Hello world!", 12));
215
216     val = vlc_tls_Shutdown(tls, false);
217     assert(val == 0);
218     vlc_join(th, &p);
219     assert(p != NULL);
220     val = vlc_tls_Read(tls, buf, sizeof (buf), false);
221     assert(val == 0);
222     vlc_tls_Close(tls);
223
224     /* Test known certificate, ignore ALPN result */
225     answer = 0;
226     val = securepair(&th, &tls, alpnv, NULL);
227     assert(val == 0);
228
229     /* Do a lot of I/O, test congestion handling */
230     static unsigned char data[16184];
231     size_t bytes = 0;
232     unsigned seed = 0;
233
234     iov.iov_base = data;
235     iov.iov_len = sizeof (data);
236
237     do
238     {
239         for (size_t i = 0; i < sizeof (data); i++)
240             data[i] = rand_r(&seed);
241         bytes += sizeof (data);
242     }
243     while ((val = tls->writev(tls, &iov, 1)) == sizeof (data));
244
245     bytes -= sizeof (data);
246     if (val > 0)
247         bytes += val;
248
249     fprintf(stderr, "Sent %zu bytes.\n", bytes);
250     seed = 0;
251
252     while (bytes > 0)
253     {
254         unsigned char c = rand_r(&seed);
255
256         val = vlc_tls_Read(tls, buf, 1, false);
257         assert(val == 1);
258         assert(c == (unsigned char)buf[0]);
259         bytes--;
260     }
261
262     vlc_tls_Close(tls);
263     vlc_join(th, NULL);
264
265     /* Test known certificate, no ALPN */
266     alpnv[0] = alpnv[1] = NULL;
267     val = securepair(&th, &tls, alpnv, NULL);
268     assert(val == 0);
269     vlc_tls_Close(tls);
270     vlc_join(th, NULL);
271
272     dialog_Unregister(obj);
273     var_DelCallback(obj, "dialog-question", question_callback, &answer);
274     vlc_tls_Delete(client_creds);
275     vlc_tls_Delete(server_creds);
276     libvlc_release(vlc);
277
278     if (fork() == 0)
279         execlp("rm", "rm", "-rf", homedir, (char *)NULL);
280     return 0;
281 }