2 * empathy-tls-verifier.c - Source for EmpathyTLSVerifier
3 * Copyright (C) 2010 Collabora Ltd.
4 * @author Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
5 * @author Stef Walter <stefw@collabora.co.uk>
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 #include "empathy-tls-verifier.h"
27 #include "empathy-utils.h"
29 #define DEBUG_FLAG EMPATHY_DEBUG_TLS
30 #include "empathy-debug.h"
32 G_DEFINE_TYPE (EmpathyTLSVerifier
, empathy_tls_verifier
,
35 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTLSVerifier);
38 PROP_TLS_CERTIFICATE
= 1,
40 PROP_REFERENCE_IDENTITIES
,
46 TpTLSCertificate
*certificate
;
48 gchar
**reference_identities
;
50 GSimpleAsyncResult
*verify_result
;
54 } EmpathyTLSVerifierPriv
;
57 verification_output_to_reason (gint res
,
59 TpTLSCertificateRejectReason
*reason
)
61 gboolean retval
= TRUE
;
63 g_assert (reason
!= NULL
);
65 if (res
!= GNUTLS_E_SUCCESS
)
69 /* the certificate is not structurally valid */
72 case GNUTLS_E_INSUFFICIENT_CREDENTIALS
:
73 *reason
= TP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED
;
75 case GNUTLS_E_CONSTRAINT_ERROR
:
76 *reason
= TP_TLS_CERTIFICATE_REJECT_REASON_LIMIT_EXCEEDED
;
79 *reason
= TP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN
;
86 /* the certificate is structurally valid, check for other errors. */
87 if (verify_output
& GNUTLS_CERT_INVALID
)
91 if (verify_output
& GNUTLS_CERT_SIGNER_NOT_FOUND
)
92 *reason
= TP_TLS_CERTIFICATE_REJECT_REASON_SELF_SIGNED
;
93 else if (verify_output
& GNUTLS_CERT_SIGNER_NOT_CA
)
94 *reason
= TP_TLS_CERTIFICATE_REJECT_REASON_UNTRUSTED
;
95 else if (verify_output
& GNUTLS_CERT_INSECURE_ALGORITHM
)
96 *reason
= TP_TLS_CERTIFICATE_REJECT_REASON_INSECURE
;
97 else if (verify_output
& GNUTLS_CERT_NOT_ACTIVATED
)
98 *reason
= TP_TLS_CERTIFICATE_REJECT_REASON_NOT_ACTIVATED
;
99 else if (verify_output
& GNUTLS_CERT_EXPIRED
)
100 *reason
= TP_TLS_CERTIFICATE_REJECT_REASON_EXPIRED
;
102 *reason
= TP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN
;
112 build_certificate_list_for_gnutls (GcrCertificateChain
*chain
,
113 gnutls_x509_crt_t
**list
,
115 gnutls_x509_crt_t
**anchors
,
118 GcrCertificate
*cert
;
120 gnutls_x509_crt_t
*retval
;
121 gnutls_x509_crt_t gcert
;
122 gnutls_datum_t datum
;
128 g_assert (n_anchors
);
130 *list
= *anchors
= NULL
;
131 *n_list
= *n_anchors
= 0;
133 length
= gcr_certificate_chain_get_length (chain
);
134 retval
= g_malloc0 (sizeof (gnutls_x509_crt_t
) * length
);
136 /* Convert the main body of the chain to gnutls */
137 for (idx
= 0; idx
< length
; ++idx
)
139 cert
= gcr_certificate_chain_get_certificate (chain
, idx
);
140 datum
.data
= (gpointer
)gcr_certificate_get_der_data (cert
, &n_data
);
143 gnutls_x509_crt_init (&gcert
);
144 if (gnutls_x509_crt_import (gcert
, &datum
, GNUTLS_X509_FMT_DER
) < 0)
145 g_return_if_reached ();
153 /* See if we have an anchor */
154 if (gcr_certificate_chain_get_status (chain
) ==
155 GCR_CERTIFICATE_CHAIN_ANCHORED
)
157 cert
= gcr_certificate_chain_get_anchor (chain
);
158 g_return_if_fail (cert
);
160 datum
.data
= (gpointer
)gcr_certificate_get_der_data (cert
, &n_data
);
163 gnutls_x509_crt_init (&gcert
);
164 if (gnutls_x509_crt_import (gcert
, &datum
, GNUTLS_X509_FMT_DER
) < 0)
165 g_return_if_reached ();
167 retval
= g_malloc0 (sizeof (gnutls_x509_crt_t
) * 1);
175 free_certificate_list_for_gnutls (gnutls_x509_crt_t
*list
,
180 for (idx
= 0; idx
< n_list
; idx
++)
181 gnutls_x509_crt_deinit (list
[idx
]);
186 complete_verification (EmpathyTLSVerifier
*self
)
188 EmpathyTLSVerifierPriv
*priv
= GET_PRIV (self
);
190 DEBUG ("Verification successful, completing...");
192 g_simple_async_result_complete_in_idle (priv
->verify_result
);
194 tp_clear_object (&priv
->verify_result
);
198 abort_verification (EmpathyTLSVerifier
*self
,
199 TpTLSCertificateRejectReason reason
)
201 EmpathyTLSVerifierPriv
*priv
= GET_PRIV (self
);
203 DEBUG ("Verification error %u, aborting...", reason
);
205 g_simple_async_result_set_error (priv
->verify_result
,
206 G_IO_ERROR
, reason
, "TLS verification failed with reason %u",
208 g_simple_async_result_complete_in_idle (priv
->verify_result
);
210 tp_clear_object (&priv
->verify_result
);
214 debug_certificate (GcrCertificate
*cert
)
216 gchar
*subject
= gcr_certificate_get_subject_dn (cert
);
217 DEBUG ("Certificate: %s", subject
);
222 debug_certificate_chain (GcrCertificateChain
*chain
)
224 GEnumClass
*enum_class
;
225 GEnumValue
*enum_value
;
227 GcrCertificate
*cert
;
229 enum_class
= G_ENUM_CLASS
230 (g_type_class_peek (GCR_TYPE_CERTIFICATE_CHAIN_STATUS
));
231 enum_value
= g_enum_get_value (enum_class
,
232 gcr_certificate_chain_get_status (chain
));
233 length
= gcr_certificate_chain_get_length (chain
);
234 DEBUG ("Certificate chain: length %u status %s",
235 length
, enum_value
? enum_value
->value_nick
: "XXX");
237 for (idx
= 0; idx
< length
; ++idx
)
239 cert
= gcr_certificate_chain_get_certificate (chain
, idx
);
240 debug_certificate (cert
);
245 perform_verification (EmpathyTLSVerifier
*self
,
246 GcrCertificateChain
*chain
)
248 gboolean ret
= FALSE
;
249 TpTLSCertificateRejectReason reason
=
250 TP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN
;
251 gnutls_x509_crt_t
*list
, *anchors
;
252 guint n_list
, n_anchors
;
256 gboolean matched
= FALSE
;
257 EmpathyTLSVerifierPriv
*priv
= GET_PRIV (self
);
259 DEBUG ("Performing verification");
260 debug_certificate_chain (chain
);
262 list
= anchors
= NULL
;
263 n_list
= n_anchors
= 0;
266 * If the first certificate is an pinned certificate then we completely
267 * ignore the rest of the verification process.
269 if (gcr_certificate_chain_get_status (chain
) == GCR_CERTIFICATE_CHAIN_PINNED
)
271 DEBUG ("Found pinned certificate for %s", priv
->hostname
);
272 complete_verification (self
);
276 build_certificate_list_for_gnutls (chain
, &list
, &n_list
,
277 &anchors
, &n_anchors
);
278 if (list
== NULL
|| n_list
== 0) {
279 g_warn_if_reached ();
280 abort_verification (self
, TP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN
);
285 res
= gnutls_x509_crt_list_verify (list
, n_list
, anchors
, n_anchors
,
286 NULL
, 0, 0, &verify_output
);
287 ret
= verification_output_to_reason (res
, verify_output
, &reason
);
289 DEBUG ("Certificate verification gave result %d with reason %u", ret
,
293 abort_verification (self
, reason
);
297 /* now check if the certificate matches one of the reference identities. */
298 if (priv
->reference_identities
!= NULL
)
300 for (i
= 0, matched
= FALSE
; priv
->reference_identities
[i
] != NULL
; ++i
)
302 if (gnutls_x509_crt_check_hostname (list
[0],
303 priv
->reference_identities
[i
]) == 1)
313 gchar
*certified_hostname
;
315 certified_hostname
= empathy_get_x509_certificate_hostname (list
[0]);
316 tp_asv_set_string (priv
->details
,
317 "expected-hostname", priv
->hostname
);
318 tp_asv_set_string (priv
->details
,
319 "certificate-hostname", certified_hostname
);
321 DEBUG ("Hostname mismatch: got %s but expected %s",
322 certified_hostname
, priv
->hostname
);
324 g_free (certified_hostname
);
325 abort_verification (self
,
326 TP_TLS_CERTIFICATE_REJECT_REASON_HOSTNAME_MISMATCH
);
330 DEBUG ("Hostname matched");
331 complete_verification (self
);
334 free_certificate_list_for_gnutls (list
, n_list
);
335 free_certificate_list_for_gnutls (anchors
, n_anchors
);
339 perform_verification_cb (GObject
*object
,
343 GError
*error
= NULL
;
345 GcrCertificateChain
*chain
= GCR_CERTIFICATE_CHAIN (object
);
346 EmpathyTLSVerifier
*self
= EMPATHY_TLS_VERIFIER (user_data
);
348 /* Even if building the chain fails, try verifying what we have */
349 if (!gcr_certificate_chain_build_finish (chain
, res
, &error
))
351 DEBUG ("Building of certificate chain failed: %s", error
->message
);
352 g_clear_error (&error
);
355 perform_verification (self
, chain
);
357 /* Matches ref when staring chain build */
358 g_object_unref (self
);
362 empathy_tls_verifier_get_property (GObject
*object
,
367 EmpathyTLSVerifierPriv
*priv
= GET_PRIV (object
);
371 case PROP_TLS_CERTIFICATE
:
372 g_value_set_object (value
, priv
->certificate
);
375 g_value_set_string (value
, priv
->hostname
);
377 case PROP_REFERENCE_IDENTITIES
:
378 g_value_set_boxed (value
, priv
->reference_identities
);
381 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
387 empathy_tls_verifier_set_property (GObject
*object
,
392 EmpathyTLSVerifierPriv
*priv
= GET_PRIV (object
);
396 case PROP_TLS_CERTIFICATE
:
397 priv
->certificate
= g_value_dup_object (value
);
400 priv
->hostname
= g_value_dup_string (value
);
402 case PROP_REFERENCE_IDENTITIES
:
403 priv
->reference_identities
= g_value_dup_boxed (value
);
406 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
412 empathy_tls_verifier_dispose (GObject
*object
)
414 EmpathyTLSVerifierPriv
*priv
= GET_PRIV (object
);
416 if (priv
->dispose_run
)
419 priv
->dispose_run
= TRUE
;
421 tp_clear_object (&priv
->certificate
);
423 G_OBJECT_CLASS (empathy_tls_verifier_parent_class
)->dispose (object
);
427 empathy_tls_verifier_finalize (GObject
*object
)
429 EmpathyTLSVerifierPriv
*priv
= GET_PRIV (object
);
431 DEBUG ("%p", object
);
433 tp_clear_boxed (G_TYPE_HASH_TABLE
, &priv
->details
);
434 g_free (priv
->hostname
);
435 g_strfreev (priv
->reference_identities
);
437 G_OBJECT_CLASS (empathy_tls_verifier_parent_class
)->finalize (object
);
441 empathy_tls_verifier_init (EmpathyTLSVerifier
*self
)
443 EmpathyTLSVerifierPriv
*priv
;
445 priv
= self
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (self
,
446 EMPATHY_TYPE_TLS_VERIFIER
, EmpathyTLSVerifierPriv
);
447 priv
->details
= tp_asv_new (NULL
, NULL
);
451 empathy_tls_verifier_class_init (EmpathyTLSVerifierClass
*klass
)
454 GObjectClass
*oclass
= G_OBJECT_CLASS (klass
);
456 g_type_class_add_private (klass
, sizeof (EmpathyTLSVerifierPriv
));
458 oclass
->set_property
= empathy_tls_verifier_set_property
;
459 oclass
->get_property
= empathy_tls_verifier_get_property
;
460 oclass
->finalize
= empathy_tls_verifier_finalize
;
461 oclass
->dispose
= empathy_tls_verifier_dispose
;
463 pspec
= g_param_spec_object ("certificate", "The TpTLSCertificate",
464 "The TpTLSCertificate to be verified.",
465 TP_TYPE_TLS_CERTIFICATE
,
466 G_PARAM_CONSTRUCT_ONLY
| G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS
);
467 g_object_class_install_property (oclass
, PROP_TLS_CERTIFICATE
, pspec
);
469 pspec
= g_param_spec_string ("hostname", "The hostname",
470 "The hostname which is certified by the certificate.",
472 G_PARAM_CONSTRUCT_ONLY
| G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS
);
473 g_object_class_install_property (oclass
, PROP_HOSTNAME
, pspec
);
475 pspec
= g_param_spec_boxed ("reference-identities",
476 "The reference identities",
477 "The certificate should certify one of these identities.",
479 G_PARAM_CONSTRUCT_ONLY
| G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS
);
480 g_object_class_install_property (oclass
, PROP_REFERENCE_IDENTITIES
, pspec
);
484 empathy_tls_verifier_new (TpTLSCertificate
*certificate
,
485 const gchar
*hostname
,
486 const gchar
**reference_identities
)
488 g_assert (TP_IS_TLS_CERTIFICATE (certificate
));
489 g_assert (hostname
!= NULL
);
490 g_assert (reference_identities
!= NULL
);
492 return g_object_new (EMPATHY_TYPE_TLS_VERIFIER
,
493 "certificate", certificate
,
494 "hostname", hostname
,
495 "reference-identities", reference_identities
,
500 empathy_tls_verifier_verify_async (EmpathyTLSVerifier
*self
,
501 GAsyncReadyCallback callback
,
504 GcrCertificateChain
*chain
;
505 GcrCertificate
*cert
;
506 GPtrArray
*cert_data
;
509 EmpathyTLSVerifierPriv
*priv
= GET_PRIV (self
);
511 DEBUG ("Starting verification");
513 g_return_if_fail (priv
->verify_result
== NULL
);
515 cert_data
= tp_tls_certificate_get_cert_data (priv
->certificate
);
516 g_return_if_fail (cert_data
);
518 priv
->verify_result
= g_simple_async_result_new (G_OBJECT (self
),
519 callback
, user_data
, NULL
);
521 /* Create a certificate chain */
522 chain
= gcr_certificate_chain_new ();
523 for (idx
= 0; idx
< cert_data
->len
; ++idx
) {
524 data
= g_ptr_array_index (cert_data
, idx
);
525 cert
= gcr_simple_certificate_new ((guchar
*) data
->data
, data
->len
);
526 gcr_certificate_chain_add (chain
, cert
);
527 g_object_unref (cert
);
530 gcr_certificate_chain_build_async (chain
, GCR_PURPOSE_SERVER_AUTH
, priv
->hostname
, 0,
531 NULL
, perform_verification_cb
, g_object_ref (self
));
533 g_object_unref (chain
);
537 empathy_tls_verifier_verify_finish (EmpathyTLSVerifier
*self
,
539 TpTLSCertificateRejectReason
*reason
,
540 GHashTable
**details
,
543 EmpathyTLSVerifierPriv
*priv
= GET_PRIV (self
);
545 if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res
),
549 *reason
= (*error
)->code
;
553 *details
= tp_asv_new (NULL
, NULL
);
554 tp_g_hash_table_update (*details
, priv
->details
,
555 (GBoxedCopyFunc
) g_strdup
,
556 (GBoxedCopyFunc
) tp_g_value_slice_dup
);
563 *reason
= TP_TLS_CERTIFICATE_REJECT_REASON_UNKNOWN
;
569 empathy_tls_verifier_store_exception (EmpathyTLSVerifier
*self
)
572 GcrCertificate
*cert
;
573 GPtrArray
*cert_data
;
574 GError
*error
= NULL
;
575 EmpathyTLSVerifierPriv
*priv
= GET_PRIV (self
);
577 cert_data
= tp_tls_certificate_get_cert_data (priv
->certificate
);
578 g_return_if_fail (cert_data
);
582 DEBUG ("No certificate to pin.");
586 /* The first certificate in the chain is for the host */
587 data
= g_ptr_array_index (cert_data
, 0);
588 cert
= gcr_simple_certificate_new ((gpointer
)data
->data
, data
->len
);
590 DEBUG ("Storing pinned certificate:");
591 debug_certificate (cert
);
593 if (!gcr_trust_add_pinned_certificate (cert
, GCR_PURPOSE_SERVER_AUTH
,
594 priv
->hostname
, NULL
, &error
))
595 DEBUG ("Can't store the pinned certificate: %s", error
->message
);
597 g_object_unref (cert
);