Merged default into xdg-dirs
[pidgin-git.git] / libpurple / tls-certificate.c
blob1740cb13516b2edaac8a2899530d106c420b83b4
1 /*
3 * purple
5 * Purple is the legal property of its developers, whose names are too numerous
6 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * source distribution.
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24 #include "internal.h"
25 #include "tls-certificate.h"
26 #include "debug.h"
27 #include "util.h"
29 /* Makes a filename path for a certificate. If id is NULL,
30 * just return the directory
32 static gchar *
33 make_certificate_path(const gchar *id)
35 return g_build_filename(purple_data_dir(),
36 "certificates", "tls",
37 id != NULL ? purple_escape_filename(id) : NULL,
38 NULL);
41 /* Creates the certificate directory if it doesn't exist,
42 * returns TRUE if it's successful or it already exists,
43 * returns FALSE if there was an error.
45 static gboolean
46 ensure_certificate_dir(GError **error)
48 gchar *dir = make_certificate_path(NULL);
49 gboolean ret = TRUE;
51 if (purple_build_dir(dir, 0700) != 0) {
52 g_set_error_literal(error, G_FILE_ERROR,
53 g_file_error_from_errno(errno),
54 g_strerror(errno));
55 ret = FALSE;
58 g_free(dir);
59 return ret;
62 GList *
63 purple_tls_certificate_list_ids()
65 gchar *dir_path;
66 GDir *dir;
67 const gchar *entry;
68 GList *idlist = NULL;
69 GError *error = NULL;
71 /* Ensure certificate directory exists */
73 if (!ensure_certificate_dir(&error)) {
74 purple_debug_error("tls-certificate",
75 "Error creating certificate directory: %s",
76 error->message);
77 g_clear_error(&error);
78 return NULL;
81 /* Open certificate directory */
83 dir_path = make_certificate_path(NULL);
84 dir = g_dir_open(dir_path, 0, &error);
86 if (dir == NULL) {
87 purple_debug_error("tls-certificate",
88 "Error opening certificate directory (%s): %s",
89 dir_path, error->message);
90 g_free(dir_path);
91 g_clear_error(&error);
92 return NULL;
95 g_free(dir_path);
97 /* Traverse the directory listing and create an idlist */
99 while ((entry = g_dir_read_name(dir)) != NULL) {
100 /* Unescape the filename
101 * (GLib owns original string)
103 const char *unescaped = purple_unescape_filename(entry);
105 /* Copy the entry name into our list
106 * (Purple own the escaped string)
108 idlist = g_list_prepend(idlist, g_strdup(unescaped));
111 g_dir_close(dir);
113 return idlist;
116 void
117 purple_tls_certificate_free_ids(GList *ids)
119 g_list_free_full(ids, g_free);
122 GTlsCertificate *
123 purple_tls_certificate_new_from_id(const gchar *id, GError **error)
125 GTlsCertificate *cert;
126 gchar *path;
128 g_return_val_if_fail(id != NULL && id[0] != '\0', NULL);
130 /* Load certificate from file if it exists */
132 path = make_certificate_path(id);
133 cert = g_tls_certificate_new_from_file(path, error);
134 g_free(path);
136 return cert;
139 gboolean
140 purple_tls_certificate_trust(const gchar *id, GTlsCertificate *certificate,
141 GError **error)
143 gchar *path;
144 gchar *pem = NULL;
145 gboolean ret;
147 g_return_val_if_fail(id != NULL && id[0] != '\0', FALSE);
148 g_return_val_if_fail(G_IS_TLS_CERTIFICATE(certificate), FALSE);
150 /* Ensure certificate directory exists */
152 if (!ensure_certificate_dir(error)) {
153 return FALSE;
156 /* Get the text representation of the certificate */
158 g_object_get(certificate, "certificate-pem", &pem, NULL);
159 g_return_val_if_fail(pem != NULL, FALSE);
161 /* Save certificate text to a fail */
163 path = make_certificate_path(id);
164 ret = g_file_set_contents(path, pem, -1, error);
165 g_free(path);
166 g_free(pem);
168 return ret;
171 gboolean
172 purple_tls_certificate_distrust(const gchar *id, GError **error)
174 gchar *path;
175 gboolean ret = TRUE;
177 g_return_val_if_fail(id != NULL && id[0] != '\0', FALSE);
179 /* Delete certificate file if it exists */
181 path = make_certificate_path(id);
183 if (g_unlink(path) != 0) {
184 g_set_error_literal(error, G_FILE_ERROR,
185 g_file_error_from_errno(errno),
186 g_strerror(errno));
187 ret = FALSE;
190 g_free(path);
192 return ret;
195 /* Converts GTlsCertificateFlags to a translated string representation
196 * of the first set error flag in the order checked
198 static const gchar *
199 tls_certificate_flags_to_reason(GTlsCertificateFlags flags)
201 if (flags & G_TLS_CERTIFICATE_UNKNOWN_CA) {
202 return _("The certificate is not trusted because no "
203 "certificate that can verify it is "
204 "currently trusted.");
205 } else if (flags & G_TLS_CERTIFICATE_BAD_IDENTITY) {
206 /* Translators: "domain" refers to a DNS domain
207 * (e.g. talk.google.com)
209 return _("The certificate presented is not issued to "
210 "this domain.");
211 } else if (flags & G_TLS_CERTIFICATE_NOT_ACTIVATED) {
212 return _("The certificate is not valid yet. Check that your "
213 "computer's date and time are accurate.");
214 } else if (flags & G_TLS_CERTIFICATE_EXPIRED) {
215 return _("The certificate has expired and should not be "
216 "considered valid. Check that your "
217 "computer's date and time are accurate.");
218 } else if (flags & G_TLS_CERTIFICATE_REVOKED) {
219 return _("The certificate has been revoked.");
220 } else if (flags & G_TLS_CERTIFICATE_INSECURE) {
221 return _("The certificate's algorithm is considered insecure.");
222 } else {
223 /* Also catches G_TLS_CERTIFICATE_GENERIC_ERROR here */
224 return _("An unknown certificate error occurred.");
228 /* Holds data for requesting the user to accept a given certificate */
229 typedef struct {
230 gchar *identity;
231 GTlsCertificate *cert;
232 } UserCertRequestData;
234 static void
235 user_cert_request_data_free(UserCertRequestData *data)
237 g_return_if_fail(data != NULL);
239 g_free(data->identity);
240 g_object_unref(data->cert);
242 g_free(data);
245 static void
246 user_cert_request_accept_cb(UserCertRequestData *data)
248 GError *error = NULL;
250 g_return_if_fail(data != NULL);
252 /* User accepted. Trust this certificate */
253 if(!purple_tls_certificate_trust(data->identity, data->cert, &error)) {
254 purple_debug_error("tls-certificate",
255 "Error trusting certificate '%s': %s",
256 data->identity, error->message);
257 g_clear_error(&error);
260 user_cert_request_data_free(data);
263 static void
264 user_cert_request_deny_cb(UserCertRequestData *data)
266 /* User denied. Free data related to the requst */
267 user_cert_request_data_free(data);
270 /* Prompts the user to accept the certificate as it failed due to the
271 * passed errors.
273 static void
274 request_accept_certificate(const gchar *identity, GTlsCertificate *peer_cert,
275 GTlsCertificateFlags errors)
277 UserCertRequestData *data;
278 gchar *primary;
280 g_return_if_fail(identity != NULL && identity[0] != '\0');
281 g_return_if_fail(G_IS_TLS_CERTIFICATE(peer_cert));
282 g_return_if_fail(errors != 0);
284 data = g_new(UserCertRequestData, 1);
285 data->identity = g_strdup(identity);
286 data->cert = g_object_ref(peer_cert);
288 primary = g_strdup_printf(_("Accept certificate for %s?"), identity);
289 purple_request_certificate(data,
290 _("TLS Certificate Verification"),
291 primary,
292 tls_certificate_flags_to_reason(errors),
293 data->cert,
294 _("Accept"), G_CALLBACK(user_cert_request_accept_cb),
295 _("Reject"), G_CALLBACK(user_cert_request_deny_cb),
296 data);
297 g_free(primary);
300 /* Called when a GTlsConnection (which this handler has been connected to)
301 * has an error validating its certificate.
302 * Returns TRUE if the certificate is already trusted, so the connection
303 * can continue.
304 * Returns FALSE if the certificate is not trusted, causing the
305 * connection's handshake to fail, and then prompts the user to accept
306 * the certificate.
308 static gboolean
309 accept_certificate_cb(GTlsConnection *conn, GTlsCertificate *peer_cert,
310 GTlsCertificateFlags errors, gpointer user_data)
312 GTlsCertificate *trusted_cert;
313 GSocketConnectable *connectable;
314 const gchar *identity;
316 g_return_val_if_fail(G_IS_TLS_CLIENT_CONNECTION(conn), FALSE);
317 g_return_val_if_fail(G_IS_TLS_CERTIFICATE(peer_cert), FALSE);
319 /* Get the certificate identity from the GTlsClientConnection */
321 connectable = g_tls_client_connection_get_server_identity(
322 G_TLS_CLIENT_CONNECTION(conn));
324 g_return_val_if_fail(G_IS_SOCKET_CONNECTABLE(connectable), FALSE);
326 /* identity is owned by the connectable */
327 if (G_IS_NETWORK_ADDRESS(connectable)) {
328 identity = g_network_address_get_hostname(
329 G_NETWORK_ADDRESS(connectable));
330 } else if (G_IS_NETWORK_SERVICE(connectable)) {
331 identity = g_network_service_get_domain(
332 G_NETWORK_SERVICE(connectable));
333 } else {
334 g_return_val_if_reached(FALSE);
337 /* See if a trusted certificate matching the peer certificate exists */
339 trusted_cert = purple_tls_certificate_new_from_id(identity, NULL);
341 if (trusted_cert != NULL &&
342 g_tls_certificate_is_same(peer_cert, trusted_cert)) {
343 /* It's manually trusted. Accept certificate */
344 g_object_unref(trusted_cert);
345 return TRUE;
348 g_clear_object(&trusted_cert);
350 /* Certificate failed and isn't trusted.
351 * Fail certificate and prompt user.
354 request_accept_certificate(identity, peer_cert, errors);
356 return FALSE;
359 gpointer
360 purple_tls_certificate_attach_to_tls_connection(GTlsConnection *conn)
362 return g_object_connect(conn, "signal::accept-certificate",
363 accept_certificate_cb, NULL, NULL);
366 /* Called when GSocketClient signals an event.
367 * Calls purple_tls_certificate_attach_to_tls_connection() on the client's
368 * connection when it's about to handshake.
370 static void
371 socket_client_event_cb(GSocketClient *client, GSocketClientEvent event,
372 GSocketConnectable *connectable, GIOStream *connection,
373 gpointer user_data)
375 if (event == G_SOCKET_CLIENT_TLS_HANDSHAKING) {
376 /* Attach libpurple's certificate subsystem to the
377 * GTlsConnection right before it starts the handshake
379 purple_tls_certificate_attach_to_tls_connection(
380 G_TLS_CONNECTION(connection));
384 gpointer
385 purple_tls_certificate_attach_to_socket_client(GSocketClient *client)
387 return g_object_connect(client, "signal::event",
388 socket_client_event_cb, NULL, NULL);