]> git.ipfire.org Git - people/ms/dma.git/blob - crypto.c
Merge pull request #34 from mtremer/better-authentication
[people/ms/dma.git] / crypto.c
1 /*
2 * Copyright (c) 2008 The DragonFly Project. All rights reserved.
3 *
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthias Schmidt <matthias@dragonflybsd.org>, University of Marburg,
6 * Germany.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
17 * distribution.
18 * 3. Neither the name of The DragonFly Project nor the names of its
19 * contributors may be used to endorse or promote products derived
20 * from this software without specific, prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36 #include <openssl/x509.h>
37 #include <openssl/md5.h>
38 #include <openssl/ssl.h>
39 #include <openssl/err.h>
40 #include <openssl/pem.h>
41 #include <openssl/rand.h>
42
43 #include <syslog.h>
44
45 #include "dma.h"
46
47 static int
48 init_cert_file(SSL_CTX *ctx, const char *path)
49 {
50 int error;
51
52 /* Load certificate into ctx */
53 error = SSL_CTX_use_certificate_chain_file(ctx, path);
54 if (error < 1) {
55 syslog(LOG_ERR, "SSL: Cannot load certificate `%s': %s", path, ssl_errstr());
56 return (-1);
57 }
58
59 /* Add private key to ctx */
60 error = SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM);
61 if (error < 1) {
62 syslog(LOG_ERR, "SSL: Cannot load private key `%s': %s", path, ssl_errstr());
63 return (-1);
64 }
65
66 /*
67 * Check the consistency of a private key with the corresponding
68 * certificate
69 */
70 error = SSL_CTX_check_private_key(ctx);
71 if (error < 1) {
72 syslog(LOG_ERR, "SSL: Cannot check private key: %s", ssl_errstr());
73 return (-1);
74 }
75
76 return (0);
77 }
78
79 int
80 smtp_init_crypto(int fd, int feature, struct smtp_features* features)
81 {
82 SSL_CTX *ctx = NULL;
83 #if (OPENSSL_VERSION_NUMBER >= 0x00909000L)
84 const SSL_METHOD *meth = NULL;
85 #else
86 SSL_METHOD *meth = NULL;
87 #endif
88 X509 *cert;
89 int error;
90
91 /* XXX clean up on error/close */
92 /* Init SSL library */
93 SSL_library_init();
94 SSL_load_error_strings();
95
96 // Allow any possible version
97 #if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
98 meth = TLS_client_method();
99 #else
100 meth = SSLv23_client_method();
101 #endif
102
103 ctx = SSL_CTX_new(meth);
104 if (ctx == NULL) {
105 syslog(LOG_WARNING, "remote delivery deferred: SSL init failed: %s", ssl_errstr());
106 return (1);
107 }
108
109 /* User supplied a certificate */
110 if (config.certfile != NULL) {
111 error = init_cert_file(ctx, config.certfile);
112 if (error) {
113 syslog(LOG_WARNING, "remote delivery deferred");
114 return (1);
115 }
116 }
117
118 /*
119 * If the user wants STARTTLS, we have to send EHLO here
120 */
121 if (((feature & SECURETRANS) != 0) &&
122 (feature & STARTTLS) != 0) {
123 /* TLS init phase, disable SSL_write */
124 config.features |= NOSSL;
125
126 if (perform_server_greeting(fd, features) == 0) {
127 send_remote_command(fd, "STARTTLS");
128 if (read_remote(fd, 0, NULL) != 2) {
129 if ((feature & TLS_OPP) == 0) {
130 syslog(LOG_ERR, "remote delivery deferred: STARTTLS not available: %s", neterr);
131 return (1);
132 } else {
133 syslog(LOG_INFO, "in opportunistic TLS mode, STARTTLS not available: %s", neterr);
134 return (0);
135 }
136 }
137 }
138
139 /* End of TLS init phase, enable SSL_write/read */
140 config.features &= ~NOSSL;
141 }
142
143 config.ssl = SSL_new(ctx);
144 if (config.ssl == NULL) {
145 syslog(LOG_NOTICE, "remote delivery deferred: SSL struct creation failed: %s",
146 ssl_errstr());
147 return (1);
148 }
149
150 /* Set ssl to work in client mode */
151 SSL_set_connect_state(config.ssl);
152
153 /* Set fd for SSL in/output */
154 error = SSL_set_fd(config.ssl, fd);
155 if (error == 0) {
156 syslog(LOG_NOTICE, "remote delivery deferred: SSL set fd failed: %s",
157 ssl_errstr());
158 return (1);
159 }
160
161 /* Open SSL connection */
162 error = SSL_connect(config.ssl);
163 if (error < 0) {
164 syslog(LOG_ERR, "remote delivery deferred: SSL handshake failed fatally: %s",
165 ssl_errstr());
166 return (1);
167 }
168
169 /* Get peer certificate */
170 cert = SSL_get_peer_certificate(config.ssl);
171 if (cert == NULL) {
172 syslog(LOG_WARNING, "remote delivery deferred: Peer did not provide certificate: %s",
173 ssl_errstr());
174 }
175 X509_free(cert);
176
177 return (0);
178 }
179
180 /*
181 * hmac_md5() taken out of RFC 2104. This RFC was written by H. Krawczyk,
182 * M. Bellare and R. Canetti.
183 *
184 * text pointer to data stream
185 * text_len length of data stream
186 * key pointer to authentication key
187 * key_len length of authentication key
188 * digest caller digest to be filled int
189 */
190 void
191 hmac_md5(unsigned char *text, int text_len, unsigned char *key, int key_len,
192 unsigned char* digest)
193 {
194 MD5_CTX context;
195 unsigned char k_ipad[65]; /* inner padding -
196 * key XORd with ipad
197 */
198 unsigned char k_opad[65]; /* outer padding -
199 * key XORd with opad
200 */
201 unsigned char tk[16];
202 int i;
203 /* if key is longer than 64 bytes reset it to key=MD5(key) */
204 if (key_len > 64) {
205
206 MD5_CTX tctx;
207
208 MD5_Init(&tctx);
209 MD5_Update(&tctx, key, key_len);
210 MD5_Final(tk, &tctx);
211
212 key = tk;
213 key_len = 16;
214 }
215
216 /*
217 * the HMAC_MD5 transform looks like:
218 *
219 * MD5(K XOR opad, MD5(K XOR ipad, text))
220 *
221 * where K is an n byte key
222 * ipad is the byte 0x36 repeated 64 times
223 *
224 * opad is the byte 0x5c repeated 64 times
225 * and text is the data being protected
226 */
227
228 /* start out by storing key in pads */
229 bzero( k_ipad, sizeof k_ipad);
230 bzero( k_opad, sizeof k_opad);
231 bcopy( key, k_ipad, key_len);
232 bcopy( key, k_opad, key_len);
233
234 /* XOR key with ipad and opad values */
235 for (i=0; i<64; i++) {
236 k_ipad[i] ^= 0x36;
237 k_opad[i] ^= 0x5c;
238 }
239 /*
240 * perform inner MD5
241 */
242 MD5_Init(&context); /* init context for 1st
243 * pass */
244 MD5_Update(&context, k_ipad, 64); /* start with inner pad */
245 MD5_Update(&context, text, text_len); /* then text of datagram */
246 MD5_Final(digest, &context); /* finish up 1st pass */
247 /*
248 * perform outer MD5
249 */
250 MD5_Init(&context); /* init context for 2nd
251 * pass */
252 MD5_Update(&context, k_opad, 64); /* start with outer pad */
253 MD5_Update(&context, digest, 16); /* then results of 1st
254 * hash */
255 MD5_Final(digest, &context); /* finish up 2nd pass */
256 }
257
258 /*
259 * CRAM-MD5 authentication
260 */
261 int
262 smtp_auth_md5(int fd, char *login, char *password)
263 {
264 unsigned char digest[BUF_SIZE];
265 char buffer[BUF_SIZE], ascii_digest[33];
266 char *temp;
267 int len, i;
268 static char hextab[] = "0123456789abcdef";
269
270 temp = calloc(BUF_SIZE, 1);
271 memset(buffer, 0, sizeof(buffer));
272 memset(digest, 0, sizeof(digest));
273 memset(ascii_digest, 0, sizeof(ascii_digest));
274
275 /* Send AUTH command according to RFC 2554 */
276 send_remote_command(fd, "AUTH CRAM-MD5");
277 if (read_remote(fd, sizeof(buffer), buffer) != 3) {
278 syslog(LOG_DEBUG, "smarthost authentication:"
279 " AUTH cram-md5 not available: %s", neterr);
280 /* if cram-md5 is not available */
281 free(temp);
282 return (-1);
283 }
284
285 /* skip 3 char status + 1 char space */
286 base64_decode(buffer + 4, temp);
287 hmac_md5((unsigned char *)temp, strlen(temp),
288 (unsigned char *)password, strlen(password), digest);
289 free(temp);
290
291 ascii_digest[32] = 0;
292 for (i = 0; i < 16; i++) {
293 ascii_digest[2*i] = hextab[digest[i] >> 4];
294 ascii_digest[2*i+1] = hextab[digest[i] & 15];
295 }
296
297 /* prepare answer */
298 snprintf(buffer, BUF_SIZE, "%s %s", login, ascii_digest);
299
300 /* encode answer */
301 len = base64_encode(buffer, strlen(buffer), &temp);
302 if (len < 0) {
303 syslog(LOG_ERR, "can not encode auth reply: %m");
304 return (-1);
305 }
306
307 /* send answer */
308 send_remote_command(fd, "%s", temp);
309 free(temp);
310 if (read_remote(fd, 0, NULL) != 2) {
311 syslog(LOG_WARNING, "remote delivery deferred:"
312 " AUTH cram-md5 failed: %s", neterr);
313 return (-2);
314 }
315
316 return (0);
317 }