initial commit
[m6w6/m6w6.github.io] / _posts / 2016-04-16-debian-disabling-sslv3-in-courier-server.md
1 ---
2 title: 'Debian: disabling SSLv3 in courier server'
3 author: m6w6
4 tags:
5 - WTF
6 - SYS
7 ---
8
9 Debian wheezy (currently oldstable) ships courier-0.68 which was probably released on 2012.
10 "Probably", because, head over to the [Courier website](http://www.courier-mta.org/) and
11 try to find the NEWS/ChangeLog.
12
13 ## The Problem
14
15 Anyway. Courier-0.68 has built-in openssl support on Debian and it initializes a SSL
16 context the following way:
17
18 ```c
19 ctx=SSL_CTX_new(protocol && strcmp(protocol, "SSL3") == 0
20 ? SSLv3_method():
21 protocol && strcmp(protocol, "SSL23") == 0
22 ? SSLv23_method():
23 TLSv1_method());
24
25 // ...
26 SSL_CTX_set_options(ctx, SSL_OP_ALL);
27
28 if (!ssl_cipher_list)
29 ssl_cipher_list="SSLv3:TLSv1:HIGH:!LOW:!MEDIUM:!EXP:!NULL:!aNULL@STRENGTH";
30
31 SSL_CTX_set_cipher_list(ctx, ssl_cipher_list);
32 ```
33
34 #### Some clarifications:
35
36 * `SSLv3_method` would **only** allow SSLv3
37 * `TLSv1_method` would **only** allow TLSv1.0
38 * `SSLv23_method` is "the general-purpose version-flexible SSL/TLS method"
39
40 So, if we do not want to limit ourselves to TLSv1.0, i.e. allow TLSv1.0, TLSv1.1 and TLSv1.2,
41 we have to limit our protocol version support through other means.
42
43 Openssl-1.0.1 (remember, we're on Debian wheezy) does neither come with `TLS_method()` the
44 generic TLS-only method, nor does it come with `SSL_CTX_set_min_proto_version` and we cannot
45 disable SSLv3 with the cipher list if we also want to allow TLSv1.0.
46
47 ## Solution 1: Upgrading to Jessie
48
49 What if we upgrade to Debian jessie (current stable)? Jessie ships courier-0.73, so let's see
50 how SSL context intitialization looks there:
51
52 ```c
53 options=SSL_OP_ALL;
54
55 method=((!protocol || !*protocol)
56 ? NULL:
57 strcmp(protocol, "SSL3") == 0
58 ? SSLv3_method():
59 strcmp(protocol, "SSL23") == 0
60 ? SSLv23_method():
61 strcmp(protocol, "TLSv1") == 0
62 ? TLSv1_method():
63 #ifdef HAVE_TLSV1_1_METHOD
64 strcmp(protocol, "TLSv1.1") == 0
65 ? TLSv1_1_method():
66 #endif
67 #ifdef HAVE_TLSV1_2_METHOD
68 strcmp(protocol, "TLSv1.2") == 0
69 ? TLSv1_2_method():
70 #endif
71 NULL);
72
73 if (!method)
74 {
75 method=SSLv23_method();
76 options|=SSL_OP_NO_SSLv2;
77 }
78
79 ctx=SSL_CTX_new(method);
80
81 // ...
82 SSL_CTX_set_options(ctx, options);
83
84 if (!ssl_cipher_list)
85 ssl_cipher_list="SSLv3:TLSv1:HIGH:!LOW:!MEDIUM:!EXP:!NULL:!aNULL@STRENGTH";
86
87 SSL_CTX_set_cipher_list(ctx, ssl_cipher_list);
88 ```
89
90 Jessie also comes with openssl-1.0.1, so the situation would not improve for our undertaking
91 by upgrading to jessie.
92
93 ## Solution 2: Augmenting SSL_CTX_new
94
95 What I came up with, is augmenting `SSL_CTX_new` and setting the desired `SSL_OP_NO_SSLv3` on
96 each newly created SSL context.
97
98 ```c
99 #include <stdio.h>
100 #include <dlfcn.h>
101 #include <openssl/ssl.h>
102
103 static void *lib;
104 static void *new_sym;
105 static void *opt_sym;
106
107 static void dl() {
108 char *error;
109
110 if (!lib) {
111 lib = dlopen("libssl.so", RTLD_LAZY|RTLD_LOCAL);
112 if (!lib) {
113 fprintf(stderr, "dlopen: %s\n", dlerror());
114 exit(1);
115 }
116 dlerror();
117 }
118
119 if (!new_sym) {
120 *(void **) &new_sym = dlsym(lib, "SSL_CTX_new");
121 if ((error = dlerror())) {
122 fprintf(stderr, "dlsym: %s\n", error);
123 dlclose(lib);
124 exit(1);
125 }
126 }
127
128 if (!opt_sym) {
129 *(void **) &opt_sym = dlsym(lib, "SSL_CTX_ctrl");
130 if ((error = dlerror())) {
131 fprintf(stderr, "dlsym: %s\n", error);
132 dlclose(lib);
133 exit(1);
134 }
135 }
136 }
137
138 SSL_CTX *SSL_CTX_new(const SSL_METHOD *m)
139 {
140 SSL_CTX *ctx;
141
142 dl();
143
144 ctx = ((SSL_CTX *(*)(const SSL_METHOD*))new_sym)(m);
145
146 if (ctx) {
147 ((long (*)(SSL_CTX *, int, long, void*))opt_sym)(ctx, SSL_CTRL_OPTIONS, SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3, NULL);
148 }
149 return ctx;
150 }
151 ```
152
153 This is the source of a tiny shared library pre-defining our `SSL_CTX_new`; to build like e.g.
154
155 `gcc -ldl -fPIC -shared -o preload.so preload.c`
156
157 It does the following:
158
159 * augments `SSL_CTX_new` with our own version,
160 i.e. whenever courier calls `SSL_CTX_new` our own version gets called
161 * when it's called the first time, it
162 * `dlopen`'s libssl
163 * fetches the addresses of the original `SSL_CTX_new` and `SSL_CTX_ctrl`
164 (which is the actual function `SSL_CTX_set_options` calls)
165 * calls the original `SSL_CTX_new` to actually create the SSL context
166 * calls `SSL_CTX_ctrl` on the new context with the options we want to set (`SSL_OP_NO_SSLv3`)
167 * returns the context to the caller
168
169 ### Usage
170
171 Courier config files are basically shell scripts which set a environment variables, so we'll
172 enable it as follows:
173
174 ```sh
175 cd /etc/courier
176 cat >>esmtpd >>esmtpd-msa >>esmtpd-ssl \
177 >>pop3d >>pop3d-ssl \
178 >>imapd >>imapd-ssl \
179 >>courierd <<EOF
180 LD_PRELOAD=/path/to/preload.so
181 EOF
182 ```
183
184 Then restart each courier service.
185
186 ### Verifying
187
188 Last, we have to verify that our solution actually works:
189
190 ```sh
191 openssl s_client \
192 -CApath /etc/ssl/certs/ \
193 -starttls imap \
194 -connect localhost:143 \
195 -crlf -quiet -ssl3 \
196 <<<LOGOUT
197 139690858608296:error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure:s3_pkt.c:1261:SSL alert number 40
198 139690858608296:error:1409E0E5:SSL routines:SSL3_WRITE_BYTES:ssl handshake failure:s3_pkt.c:599:
199 ```
200
201 A bunch of errors. "Good"! :)
202
203 Let's see if we can still connect with TLSv1+:
204
205 ```sh
206 openssl s_client \
207 -CApath /etc/ssl/certs/ \
208 -starttls imap \
209 -connect localhost:143 \
210 -crlf -quiet -tls1 \
211 <<<LOGOUT
212 depth=2 ...
213 verify return:1
214 depth=1 ...
215 verify return:1
216 depth=0 ...
217 verify return:1
218 . OK CAPABILITY completed
219 * BYE Courier-IMAP server shutting down
220 LOGOUT OK LOGOUT completed
221 ```
222
223 Awesome. Mission accomplished.
224
225 ## Addendum
226
227 Here's my cipher list for the interested:
228
229 ```sh
230 openssl ciphers -v 'HIGH+aRSA:+kEDH:+kRSA:+SHA:+3DES:!kSRP' \
231 | awk '{ printf "%-28s %-8s %-8s %-18s %-16s\n",$1,$2,$3,$5,$6 }'
232 ```
233
234 Which results in the following ciphers:
235
236 ```
237 ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Enc=AESGCM(256) Mac=AEAD
238 ECDHE-RSA-AES256-SHA384 TLSv1.2 Kx=ECDH Enc=AES(256) Mac=SHA384
239 ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Enc=AESGCM(128) Mac=AEAD
240 ECDHE-RSA-AES128-SHA256 TLSv1.2 Kx=ECDH Enc=AES(128) Mac=SHA256
241 DHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=DH Enc=AESGCM(256) Mac=AEAD
242 DHE-RSA-AES256-SHA256 TLSv1.2 Kx=DH Enc=AES(256) Mac=SHA256
243 DHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=DH Enc=AESGCM(128) Mac=AEAD
244 DHE-RSA-AES128-SHA256 TLSv1.2 Kx=DH Enc=AES(128) Mac=SHA256
245 AES256-GCM-SHA384 TLSv1.2 Kx=RSA Enc=AESGCM(256) Mac=AEAD
246 AES256-SHA256 TLSv1.2 Kx=RSA Enc=AES(256) Mac=SHA256
247 AES128-GCM-SHA256 TLSv1.2 Kx=RSA Enc=AESGCM(128) Mac=AEAD
248 AES128-SHA256 TLSv1.2 Kx=RSA Enc=AES(128) Mac=SHA256
249 ECDHE-RSA-AES256-SHA SSLv3 Kx=ECDH Enc=AES(256) Mac=SHA1
250 ECDHE-RSA-AES128-SHA SSLv3 Kx=ECDH Enc=AES(128) Mac=SHA1
251 DHE-RSA-AES256-SHA SSLv3 Kx=DH Enc=AES(256) Mac=SHA1
252 DHE-RSA-CAMELLIA256-SHA SSLv3 Kx=DH Enc=Camellia(256) Mac=SHA1
253 DHE-RSA-AES128-SHA SSLv3 Kx=DH Enc=AES(128) Mac=SHA1
254 DHE-RSA-CAMELLIA128-SHA SSLv3 Kx=DH Enc=Camellia(128) Mac=SHA1
255 AES256-SHA SSLv3 Kx=RSA Enc=AES(256) Mac=SHA1
256 CAMELLIA256-SHA SSLv3 Kx=RSA Enc=Camellia(256) Mac=SHA1
257 AES128-SHA SSLv3 Kx=RSA Enc=AES(128) Mac=SHA1
258 CAMELLIA128-SHA SSLv3 Kx=RSA Enc=Camellia(128) Mac=SHA1
259 ECDHE-RSA-DES-CBC3-SHA SSLv3 Kx=ECDH Enc=3DES(168) Mac=SHA1
260 EDH-RSA-DES-CBC3-SHA SSLv3 Kx=DH Enc=3DES(168) Mac=SHA1
261 DES-CBC3-SHA SSLv3 Kx=RSA Enc=3DES(168) Mac=SHA1
262 ```
263
264 Note, that courier actually does **not** support `ECDH` key exchange, but I didn't exclude it for
265 the sake of simplicity for using the same cipher list for every server (e.g. web etc.)