static gboolean
infinoted_startup_load_credentials(InfinotedStartup* startup,
                                   GError** error)
{
  gnutls_x509_crt_t* certs;
  guint n_certs;
  gnutls_certificate_credentials_t creds;
  int res;

  if(startup->options->security_policy !=
     INF_XMPP_CONNECTION_SECURITY_ONLY_UNSECURED)
  {
    startup->private_key = infinoted_startup_load_key(
      startup->log,
      startup->options->create_key,
      startup->options->key_file,
      error
    );

    if(startup->private_key == NULL)
      return FALSE;

    certs = infinoted_startup_load_certificate(
      startup->log,
      startup->options->create_certificate,
      startup->private_key,
      startup->options->certificate_file,
      startup->options->certificate_chain_file,
      &n_certs,
      error
    );

    if(certs == NULL)
      return FALSE;

    /* Takes ownership of certificates: */
    startup->certificates = inf_certificate_chain_new(certs, n_certs);

    startup->credentials = inf_certificate_credentials_new();
    creds = inf_certificate_credentials_get(startup->credentials);

    res = gnutls_certificate_set_x509_key(
      creds,
      inf_certificate_chain_get_raw(startup->certificates),
      inf_certificate_chain_get_n_certificates(startup->certificates),
      startup->private_key
    );

    if(res != 0)
    {
      inf_gnutls_set_error(error, res);
      return FALSE;
    }
  }

  return TRUE;
}
static void
infinoted_plugin_certificate_auth_certificate_func(InfXmppConnection* xmpp,
                                                   gnutls_session_t session,
                                                   InfCertificateChain* chain,
                                                   gpointer user_data)
{
  InfinotedPluginCertificateAuth* plugin;
  int res;
  int verify_result;
  GError* error;

  plugin = (InfinotedPluginCertificateAuth*)user_data;

  if(chain != NULL)
  {
    /* Note that we don't require client certificates to be signed by a CA.
     * We only require them to be signed by one of the certificates in our
     * list, but we don't care whether that's a CA or not. A common use case
     * is to sign client certificates with our own server certificate. */
    res = gnutls_x509_crt_list_verify(
      inf_certificate_chain_get_raw(chain),
      inf_certificate_chain_get_n_certificates(chain),
      plugin->cas,
      plugin->n_cas,
      NULL,
      0,
      GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT | GNUTLS_VERIFY_DISABLE_CA_SIGN,
      &verify_result
    );

    error = NULL;

    if(res != GNUTLS_E_SUCCESS)
      inf_gnutls_set_error(&error, res);
    else if( (verify_result & GNUTLS_CERT_INVALID) != 0)
      inf_gnutls_certificate_verification_set_error(&error, res);

    if(error != NULL)
    {
      inf_xmpp_connection_certificate_verify_cancel(xmpp, error);
      g_error_free(error);
    }
    else
    {
      inf_xmpp_connection_certificate_verify_continue(xmpp);
    }
  }
  else
  {
    /* If we do not accept unauthenticated clients, then GnuTLS should have
     * blocked the connection already, since we set the certificate request
     * to GNUTLS_CERT_REQUIRE in that case. */
    g_assert(plugin->accept_unauthenticated_clients == TRUE);
    inf_xmpp_connection_certificate_verify_continue(xmpp);
  }
}
void Gobby::CertificateManager::make_credentials()
{
	InfCertificateCredentials* creds = inf_certificate_credentials_new();
	gnutls_certificate_credentials_t gnutls_creds =
		inf_certificate_credentials_get(creds);

	if(m_preferences.security.authentication_enabled &&
	   m_key != NULL && m_certificates != NULL)
	{
		gnutls_certificate_set_x509_key(
			gnutls_creds,
			inf_certificate_chain_get_raw(m_certificates),
			inf_certificate_chain_get_n_certificates(m_certificates),
			m_key
		);
	}

	if(m_preferences.security.use_system_trust)
	{
		const int n_cas = gnutls_certificate_set_x509_system_trust(
			gnutls_creds);
		if(n_cas < 0)
		{
			g_warning("Failed to add system CAs: %s\n",
				gnutls_strerror(n_cas));
		}
	}

	if(!m_trust.empty())
	{
		gnutls_certificate_set_x509_trust(
			gnutls_creds,
			&m_trust[0],
			m_trust.size()
		);
	}

	if(m_dh_params != NULL)
		gnutls_certificate_set_dh_params(gnutls_creds, m_dh_params);

	gnutls_certificate_set_verify_flags(
		gnutls_creds, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);

	InfCertificateCredentials* old_creds = m_credentials;
	m_credentials = creds;

	m_signal_credentials_changed.emit();

	if(old_creds != NULL)
		inf_certificate_credentials_unref(old_creds);
}
static void
inf_gtk_certificate_manager_certificate_func(InfXmppConnection* connection,
                                             gnutls_session_t session,
                                             InfCertificateChain* chain,
                                             gpointer user_data)
{
  InfGtkCertificateManager* manager;
  InfGtkCertificateManagerPrivate* priv;

  InfGtkCertificateDialogFlags flags;
  gnutls_x509_crt_t presented_cert;
  gnutls_x509_crt_t known_cert;
  gchar* hostname;

  gboolean match_hostname;
  gboolean issuer_known;
  gnutls_x509_crt_t root_cert;

  int ret;
  unsigned int verify;
  GHashTable* table;
  gboolean cert_equal;
  time_t expiration_time;

  InfGtkCertificateManagerQuery* query;
  gchar* text;
  GtkWidget* vbox;
  GtkWidget* button;
  GtkWidget* image;
  GtkWidget* label;

  GError* error;

  manager = INF_GTK_CERTIFICATE_MANAGER(user_data);
  priv = INF_GTK_CERTIFICATE_MANAGER_PRIVATE(manager);

  g_object_get(G_OBJECT(connection), "remote-hostname", &hostname, NULL);
  presented_cert = inf_certificate_chain_get_own_certificate(chain);

  match_hostname = gnutls_x509_crt_check_hostname(presented_cert, hostname);

  /* First, validate the certificate */
  ret = gnutls_certificate_verify_peers2(session, &verify);
  error = NULL;

  if(ret != GNUTLS_E_SUCCESS)
    inf_gnutls_set_error(&error, ret);

  /* Remove the GNUTLS_CERT_ISSUER_NOT_KNOWN flag from the verification
   * result, and if the certificate is still invalid, then set an error. */
  if(error == NULL)
  {
    issuer_known = TRUE;
    if(verify & GNUTLS_CERT_SIGNER_NOT_FOUND)
    {
      issuer_known = FALSE;

      /* Re-validate the certificate for other failure reasons --
       * unfortunately the gnutls_certificate_verify_peers2() call
       * does not tell us whether the certificate is otherwise invalid
       * if a signer is not found already. */
      /* TODO: Here it would be good to use the verify flags from the
       * certificate credentials, but GnuTLS does not have API to
       * retrieve them. */
      root_cert = inf_certificate_chain_get_root_certificate(chain);

      ret = gnutls_x509_crt_list_verify(
        inf_certificate_chain_get_raw(chain),
        inf_certificate_chain_get_n_certificates(chain),
        &root_cert,
        1,
        NULL,
        0,
        GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT,
        &verify
      );

      if(ret != GNUTLS_E_SUCCESS)
        inf_gnutls_set_error(&error, ret);
      else if(verify & GNUTLS_CERT_INVALID)
        inf_gnutls_certificate_verification_set_error(&error, verify);
    }
  }

  /* Look up the host in our database of pinned certificates if we could not
   * fully verify the certificate, i.e. if either the issuer is not known or
   * the hostname of the connection does not match the certificate. */
  table = NULL;
  if(error == NULL)
  {
    known_cert = NULL;
    if(!match_hostname || !issuer_known)
    {
      /* If we cannot load the known host file, then cancel the connection.
       * Otherwise it might happen that someone shows us a certificate that we
       * tell the user we don't know, if though actually for that host we expect
       * a different certificate. */
      table = inf_gtk_certificate_manager_ref_known_hosts(manager, &error);
      if(table != NULL)
        known_cert = g_hash_table_lookup(table, hostname);
    }
  }

  /* Next, configure the flags for the dialog to be shown based on the
   * verification result, and on whether the pinned certificate matches
   * the one presented by the host or not. */
  flags = 0;
  if(error == NULL)
  {
    if(known_cert != NULL)
    {
      cert_equal = inf_gtk_certificate_manager_compare_fingerprint(
        known_cert,
        presented_cert,
        &error
      );

      if(error == NULL && cert_equal == FALSE)
      {
        if(!match_hostname)
          flags |= INF_GTK_CERTIFICATE_DIALOG_CERT_HOSTNAME_MISMATCH;
        if(!issuer_known)
          flags |= INF_GTK_CERTIFICATE_DIALOG_CERT_ISSUER_NOT_KNOWN;

        flags |= INF_GTK_CERTIFICATE_DIALOG_CERT_UNEXPECTED;
        expiration_time = gnutls_x509_crt_get_expiration_time(known_cert);
        if(expiration_time != (time_t)(-1))
        {
          expiration_time -= INF_GTK_CERTIFICATE_MANAGER_EXPIRATION_TOLERANCE;
          if(time(NULL) > expiration_time)
          {
            flags |= INF_GTK_CERTIFICATE_DIALOG_CERT_OLD_EXPIRED;
          }
        }
      }
    }
    else
    {
      if(!match_hostname)
        flags |= INF_GTK_CERTIFICATE_DIALOG_CERT_HOSTNAME_MISMATCH;
      if(!issuer_known)
        flags |= INF_GTK_CERTIFICATE_DIALOG_CERT_ISSUER_NOT_KNOWN;
    }
  }

  /* Now proceed either by accepting the connection, rejecting it, or
   * bothering the user with an annoying dialog. */
  if(error == NULL)
  {
    if(flags == 0)
    {
      if(match_hostname && issuer_known)
      {
        /* Remove the pinned entry if we now have a valid certificate for
         * this host. */
        if(table != NULL && g_hash_table_remove(table, hostname) == TRUE)
        {
          inf_gtk_certificate_manager_write_known_hosts_with_warning(
            manager,
            table
          );
        }
      }

      inf_xmpp_connection_certificate_verify_continue(connection);
    }
    else
    {
      query = g_slice_new(InfGtkCertificateManagerQuery);
      query->manager = manager;
      query->known_hosts = table;
      query->connection = connection;
      query->dialog = inf_gtk_certificate_dialog_new(
        priv->parent_window,
        0,
        flags,
        hostname,
        chain
      );
      query->certificate_chain = chain;

      table = NULL;

      g_object_ref(query->connection);
      inf_certificate_chain_ref(chain);

      g_signal_connect(
        G_OBJECT(connection),
        "notify::status",
        G_CALLBACK(inf_gtk_certificate_manager_notify_status_cb),
        query
      );

      g_signal_connect(
        G_OBJECT(query->dialog),
        "response",
        G_CALLBACK(inf_gtk_certificate_manager_response_cb),
        query
      );

      image = gtk_image_new_from_stock(GTK_STOCK_CANCEL, GTK_ICON_SIZE_BUTTON);
      gtk_widget_show(image);

      button = gtk_dialog_add_button(
        GTK_DIALOG(query->dialog),
        _("_Cancel connection"),
        GTK_RESPONSE_REJECT
      );

      gtk_button_set_image(GTK_BUTTON(button), image);

      image = gtk_image_new_from_stock(GTK_STOCK_CONNECT, GTK_ICON_SIZE_BUTTON);
      gtk_widget_show(image);

      button = gtk_dialog_add_button(
        GTK_DIALOG(query->dialog),
        _("C_ontinue connection"),
        GTK_RESPONSE_ACCEPT
      );

      gtk_button_set_image(GTK_BUTTON(button), image);

      text = g_strdup_printf(
        _("Do you want to continue the connection to host \"%s\"? If you "
          "choose to continue, this certificate will be trusted in the "
          "future when connecting to this host."),
        hostname
      );

      label = gtk_label_new(text);
      gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
      gtk_label_set_line_wrap_mode(GTK_LABEL(label), PANGO_WRAP_WORD_CHAR);
      gtk_label_set_width_chars(GTK_LABEL(label), 60);
      gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
      gtk_widget_show(label);
      g_free(text);

      vbox = gtk_dialog_get_content_area(GTK_DIALOG(query->dialog));
      gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);

      priv->queries = g_slist_prepend(priv->queries, query);
      gtk_window_present(GTK_WINDOW(query->dialog));
    }
  }
  else
  {
    inf_xmpp_connection_certificate_verify_cancel(connection, error);
    g_error_free(error);
  }

  if(table != NULL) g_hash_table_unref(table);
  g_free(hostname);
}
static void
inf_test_certificate_request_finished_cb(InfRequest* request,
                                         const InfRequestResult* result,
                                         const GError* error,
                                         gpointer user_data)
{
  InfTestCertificateRequest* test;
  InfCertificateChain* chain;
  guint n_certs;
  guint i;
  gnutls_datum_t datum;

  gnutls_x509_crt_t cert;
  size_t cert_size;
  gchar* cert_pem;

  test = (InfTestCertificateRequest*)user_data;

  if(error != NULL)
  {
    fprintf(stderr, "Error: %s\n", error->message);
  }
  else
  {
    fprintf(stderr, "Certificate generated!\n\n");
    inf_request_result_get_create_acl_account(result, NULL, NULL, &chain);

    n_certs = inf_certificate_chain_get_n_certificates(chain);
    for(i = 0; i < n_certs; ++i)
    {
      fprintf(stderr, "Certificate %d", i);
      if(i == 0) fprintf(stderr, " (own)");
      if(i == 1) fprintf(stderr, " (issuer)");
      if(i == n_certs - 1) fprintf(stderr, " (CA)");
      fprintf(stderr, ":\n\n");

      cert = inf_certificate_chain_get_nth_certificate(chain, i);
      gnutls_x509_crt_print(cert, GNUTLS_CRT_PRINT_FULL, &datum);

      fprintf(stderr, "%s\n", datum.data);

      gnutls_free(datum.data);
    }

    for(i = 0; i < n_certs; ++i)
    {
      cert = inf_certificate_chain_get_nth_certificate(chain, i);
      cert_size = 0;
      gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_PEM, NULL, &cert_size);
      cert_pem = g_malloc(cert_size);
      gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_PEM, cert_pem, &cert_size);
      printf("%s\n\n", cert_pem);
      g_free(cert_pem);
    }

    cert_size = 0;
    gnutls_x509_privkey_export(
      test->key,
      GNUTLS_X509_FMT_PEM,
      NULL,
      &cert_size
    );

    cert_pem = g_malloc(cert_size);

    gnutls_x509_privkey_export(
      test->key,
      GNUTLS_X509_FMT_PEM,
      cert_pem,
      &cert_size
    );

    printf("%s\n", cert_pem);
    g_free(cert_pem);
  }

  if(inf_standalone_io_loop_running(test->io))
    inf_standalone_io_loop_quit(test->io);
}
void Gobby::BrowserContextCommands::on_account_created(
	gnutls_x509_privkey_t key,
	InfCertificateChain* certificate,
	const InfAclAccount* account)
{
	InfBrowser* browser;
	g_object_get(G_OBJECT(m_dialog->gobj()), "browser", &browser, NULL);
	const InfAclAccount* own_account =
		inf_browser_get_acl_local_account(browser);
	const InfAclAccountId default_id =
		inf_acl_account_id_from_string("default");
	const bool is_default_account = (own_account->id == default_id);
	g_object_unref(browser);

	gnutls_x509_crt_t cert =
		inf_certificate_chain_get_own_certificate(certificate);
	gchar* name = inf_cert_util_get_dn_by_oid(
		cert, GNUTLS_OID_X520_COMMON_NAME, 0);
	const std::string cn = name;
	g_free(name);

	std::string host;
	if(INFC_IS_BROWSER(browser))
	{
		InfXmlConnection* xml =
			infc_browser_get_connection(INFC_BROWSER(browser));
		if(INF_IS_XMPP_CONNECTION(xml))
		{
			gchar* hostname;
			g_object_get(G_OBJECT(xml), "remote-hostname",
			             &hostname, NULL);
			host = hostname;
			g_free(hostname);
		}
	}

	if(host.empty())
		host = "local";

	gnutls_x509_crt_t* certs = inf_certificate_chain_get_raw(certificate);
	const unsigned int n_certs =
		inf_certificate_chain_get_n_certificates(certificate);

	std::auto_ptr<Gtk::MessageDialog> dlg;

	try
	{
		const std::string filename = make_certificate_filename(cn, host);

		GError* error = NULL;
		inf_cert_util_write_certificate_with_key(
			key, certs, n_certs, filename.c_str(), &error);

		if(error != NULL)
		{
			const std::string message = error->message;
			g_error_free(error);
			throw std::runtime_error(message);
		}

		if(is_default_account &&
		   (m_cert_manager.get_private_key() == NULL ||
		    m_cert_manager.get_certificates() == NULL))
		{
			m_preferences.security.key_file = filename;
			m_preferences.security.certificate_file = filename;

			dlg.reset(new Gtk::MessageDialog(
				m_parent, _("Account successfully created"),
				false, Gtk::MESSAGE_INFO,
				Gtk::BUTTONS_CLOSE));
			dlg->set_secondary_text(
				_("When re-connecting to the server, the "
				  "new account will be used."));
		}
		else
		{
			// TODO: Gobby should support multiple certificates
			dlg.reset(new Gtk::MessageDialog(
				m_parent, _("Account successfully created"),
				false, Gtk::MESSAGE_INFO,
				Gtk::BUTTONS_CLOSE));
			dlg->set_secondary_text(Glib::ustring::compose(
				_("The certificate has been stored at %1.\n\n"
				  "To login to this account, set the "
				  "certificate in Gobby's preferences "
				  "and re-connect to the server."),
				filename));
		}
	}
	catch(const std::exception& ex)
	{
		// This is actually a bit unfortunate, because the
		// account was actually created and we have a valid
		// certificate for it, but we cannot keep it...
		dlg.reset(new Gtk::MessageDialog(
			m_parent, _("Failed to create account"),
			false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE));
		dlg->set_secondary_text(Glib::ustring::compose(
			_("Could not save the certificate for the "
			  "account: %1"), ex.what()));
	}

	m_dialog = dlg;
	m_dialog->signal_response().connect(
		sigc::mem_fun(
			*this,
			&BrowserContextCommands::
				on_account_created_response));
	m_dialog->present();
}