chiark / gitweb /
journal-gatewayd: check if certificate is signed by CA
[elogind.git] / src / journal / microhttpd-util.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Lennart Poettering
7   Copyright 2012 Zbigniew JÄ™drzejewski-Szmek
8
9   systemd is free software; you can redistribute it and/or modify it
10   under the terms of the GNU Lesser General Public License as published by
11   the Free Software Foundation; either version 2.1 of the License, or
12   (at your option) any later version.
13
14   systemd is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public License
20   along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <stddef.h>
24 #include <stdio.h>
25 #include <string.h>
26
27 #include "microhttpd-util.h"
28 #include "log.h"
29 #include "macro.h"
30 #include "util.h"
31
32 #ifdef HAVE_GNUTLS
33 #include <gnutls/gnutls.h>
34 #include <gnutls/x509.h>
35 #endif
36
37 void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
38         _cleanup_free_ char *f = NULL;
39
40         if (asprintf(&f, "microhttpd: %s", fmt) <= 0) {
41                 log_oom();
42                 return;
43         }
44
45         DISABLE_WARNING_FORMAT_NONLITERAL;
46         log_metav(LOG_INFO, NULL, 0, NULL, f, ap);
47         REENABLE_WARNING;
48 }
49
50
51 int respond_oom_internal(struct MHD_Connection *connection) {
52         struct MHD_Response *response;
53         const char m[] = "Out of memory.\n";
54         int ret;
55
56         assert(connection);
57
58         response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
59         if (!response)
60                 return MHD_NO;
61
62         MHD_add_response_header(response, "Content-Type", "text/plain");
63         ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
64         MHD_destroy_response(response);
65
66         return ret;
67 }
68
69 _printf_(3,4)
70 int respond_error(struct MHD_Connection *connection,
71                   unsigned code,
72                   const char *format, ...) {
73
74         struct MHD_Response *response;
75         char *m;
76         int r;
77         va_list ap;
78
79         assert(connection);
80         assert(format);
81
82         va_start(ap, format);
83         r = vasprintf(&m, format, ap);
84         va_end(ap);
85
86         if (r < 0)
87                 return respond_oom(connection);
88
89         response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
90         if (!response) {
91                 free(m);
92                 return respond_oom(connection);
93         }
94
95         log_debug("queing response %u: %s", code, m);
96         MHD_add_response_header(response, "Content-Type", "text/plain");
97         r = MHD_queue_response(connection, code, response);
98         MHD_destroy_response(response);
99
100         return r;
101 }
102
103 #ifdef HAVE_GNUTLS
104
105 static int log_level_map[] = {
106         LOG_DEBUG,
107         LOG_WARNING, /* gnutls session audit */
108         LOG_DEBUG,   /* gnutls debug log */
109         LOG_WARNING, /* gnutls assert log */
110         LOG_INFO,    /* gnutls handshake log */
111         LOG_DEBUG,   /* gnutls record log */
112         LOG_DEBUG,   /* gnutls dtls log */
113         LOG_DEBUG,
114         LOG_DEBUG,
115         LOG_DEBUG,
116         LOG_DEBUG,   /* gnutls hard log */
117         LOG_DEBUG,   /* gnutls read log */
118         LOG_DEBUG,   /* gnutls write log */
119         LOG_DEBUG,   /* gnutls io log */
120         LOG_DEBUG,   /* gnutls buffers log */
121 };
122
123 void log_func_gnutls(int level, const char *message) {
124         int ourlevel;
125
126         assert_se(message);
127
128         if (0 <= level && level < (int) ELEMENTSOF(log_level_map))
129                 ourlevel = log_level_map[level];
130         else
131                 level = LOG_DEBUG;
132
133         log_meta(ourlevel, NULL, 0, NULL, "gnutls: %s", message);
134 }
135
136 static int verify_cert_authorized(gnutls_session_t session) {
137         unsigned status;
138         gnutls_certificate_type_t type;
139         gnutls_datum_t out;
140         int r;
141
142         r = gnutls_certificate_verify_peers2(session, &status);
143         if (r < 0) {
144                 log_error("gnutls_certificate_verify_peers2 failed: %s", strerror(-r));
145                 return r;
146         }
147
148         type = gnutls_certificate_type_get(session);
149         r = gnutls_certificate_verification_status_print(status, type, &out, 0);
150         if (r < 0) {
151                 log_error("gnutls_certificate_verification_status_print failed: %s", strerror(-r));
152                 return r;
153         }
154
155         log_info("Certificate status: %s", out.data);
156
157         return status == 0 ? 0 : -EPERM;
158 }
159
160 static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) {
161         const gnutls_datum_t *pcert;
162         unsigned listsize;
163         gnutls_x509_crt_t cert;
164         int r;
165
166         assert(session);
167         assert(client_cert);
168
169         pcert = gnutls_certificate_get_peers(session, &listsize);
170         if (!pcert || !listsize) {
171                 log_error("Failed to retrieve certificate chain");
172                 return -EINVAL;
173         }
174
175         r = gnutls_x509_crt_init(&cert);
176         if (r < 0) {
177                 log_error("Failed to initialize client certificate");
178                 return r;
179         }
180
181         /* Note that by passing values between 0 and listsize here, you
182            can get access to the CA's certs */
183         r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER);
184         if (r < 0) {
185                 log_error("Failed to import client certificate");
186                 gnutls_x509_crt_deinit(cert);
187                 return r;
188         }
189
190         *client_cert = cert;
191         return 0;
192 }
193
194 static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
195         size_t len = 0;
196         int r;
197
198         assert(buf);
199         assert(*buf == NULL);
200
201         r = gnutls_x509_crt_get_dn(client_cert, NULL, &len);
202         if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) {
203                 log_error("gnutls_x509_crt_get_dn failed");
204                 return r;
205         }
206
207         *buf = malloc(len);
208         if (!*buf)
209                 return log_oom();
210
211         gnutls_x509_crt_get_dn(client_cert, *buf, &len);
212         return 0;
213 }
214
215 int check_permissions(struct MHD_Connection *connection, int *code) {
216         const union MHD_ConnectionInfo *ci;
217         gnutls_session_t session;
218         gnutls_x509_crt_t client_cert;
219         char _cleanup_free_ *buf = NULL;
220         int r;
221
222         assert(connection);
223         assert(code);
224
225         *code = 0;
226
227         ci = MHD_get_connection_info(connection,
228                                      MHD_CONNECTION_INFO_GNUTLS_SESSION);
229         if (!ci) {
230                 log_error("MHD_get_connection_info failed");
231                 return -EINVAL;
232         }
233         session = ci->tls_session;
234         assert(session);
235
236         r = get_client_cert(session, &client_cert);
237         if (r < 0) {
238                 *code = respond_error(connection, MHD_HTTP_UNAUTHORIZED,
239                                       "Authorization through certificate is required");
240                 return -EPERM;
241         }
242
243         r = get_auth_dn(client_cert, &buf);
244         if (r < 0) {
245                 *code = respond_error(connection, MHD_HTTP_UNAUTHORIZED,
246                                       "Failed to determine distinguished name from certificate");
247                 return -EPERM;
248         }
249
250         log_info("Connection from %s", buf);
251
252         r = verify_cert_authorized(session);
253         if (r < 0) {
254                 log_error("Client is not authorized");
255                 *code = respond_error(connection, MHD_HTTP_UNAUTHORIZED,
256                                       "Client certificate not signed by recognized authority");
257         }
258         return r;
259 }
260
261 #else
262 int check_permissions(struct MHD_Connection *connection, int *code) {
263         return -EPERM;
264 }
265 #endif