Function: gnutls-boot

gnutls-boot is a function defined in gnutls.c.

Signature

(gnutls-boot PROC TYPE PROPLIST)

Documentation

Initialize GnuTLS client for process PROC with TYPE+PROPLIST.

Currently only client mode is supported. Return a success/failure value you can check with gnutls-errorp.

TYPE is a symbol, either gnutls-anon or gnutls-x509pki. PROPLIST is a property list with the following keys:

:hostname is a string naming the remote host.

:priority is a GnuTLS priority string, defaults to "NORMAL".

:trustfiles is a list of PEM-encoded trust files for gnutls-x509pki.

:crlfiles is a list of PEM-encoded CRL lists for gnutls-x509pki.

:keylist is an alist of PEM-encoded key files and PEM-encoded
certificates for gnutls-x509pki.

:callbacks is an alist of callback functions, see below.

:loglevel is the debug level requested from GnuTLS, try 4.

:verify-flags is a bitset as per GnuTLS'
gnutls_certificate_set_verify_flags.

:verify-hostname-error is ignored. Pass :hostname in :verify-error
instead.

:verify-error is a list of symbols to express verification checks or
t to do all checks. Currently it can contain :trustfiles and
:hostname to verify the certificate or the hostname respectively.

:min-prime-bits is the minimum accepted number of bits the client will
accept in Diffie-Hellman key exchange.

:complete-negotiation, if non-nil, will make negotiation complete
before returning even on non-blocking sockets.

The debug level will be set for this process AND globally for GnuTLS. So if you set it higher or lower at any point, it affects global debugging.

Note that the priority is set on the client. The server does not use the protocols's priority except for disabling protocols that were not specified.

Processes must be initialized with this function before other GnuTLS functions are used. This function allocates resources which can only be deallocated by calling gnutls-deinit or by calling it again.

The callbacks alist can have a verify key, associated with a verification function (UNUSED).

Each authentication type may need additional information in order to work. For X.509 PKI (gnutls-x509pki), you probably need at least one trustfile (usually a CA bundle).

Probably introduced at or before Emacs version 26.1.

Source Code

// Defined in /usr/src/emacs/src/gnutls.c
// Skipping highlighting due to helpful-max-highlight.
{
  int ret = GNUTLS_E_SUCCESS;
  int max_log_level = 0;

  gnutls_session_t state;
  gnutls_certificate_credentials_t x509_cred = NULL;
  gnutls_anon_client_credentials_t anon_cred = NULL;
  Lisp_Object global_init;
  char const *priority_string_ptr = "NORMAL"; /* default priority string.  */
  char *c_hostname;

  /* Placeholders for the property list elements.  */
  Lisp_Object priority_string;
  Lisp_Object trustfiles;
  Lisp_Object crlfiles;
  Lisp_Object keylist;
  /* Lisp_Object callbacks; */
  Lisp_Object loglevel;
  Lisp_Object hostname;
  Lisp_Object prime_bits;
  struct Lisp_Process *p = XPROCESS (proc);

  CHECK_PROCESS (proc);
  CHECK_SYMBOL (type);
  CHECK_LIST (proplist);

  if (NILP (Fgnutls_available_p ()))
    {
      boot_error (p, "GnuTLS not available");
      return Qnil;
    }

  if (!EQ (type, Qgnutls_x509pki) && !EQ (type, Qgnutls_anon))
    {
      boot_error (p, "Invalid GnuTLS credential type");
      return Qnil;
    }

  hostname              = Fplist_get (proplist, QChostname);
  priority_string       = Fplist_get (proplist, QCpriority);
  trustfiles            = Fplist_get (proplist, QCtrustfiles);
  keylist               = Fplist_get (proplist, QCkeylist);
  crlfiles              = Fplist_get (proplist, QCcrlfiles);
  loglevel              = Fplist_get (proplist, QCloglevel);
  prime_bits            = Fplist_get (proplist, QCmin_prime_bits);

  if (!STRINGP (hostname))
    {
      boot_error (p, "gnutls-boot: invalid :hostname parameter (not a string)");
      return Qnil;
    }
  c_hostname = SSDATA (hostname);

  state = XPROCESS (proc)->gnutls_state;

  if (INTEGERP (loglevel))
    {
      gnutls_global_set_log_function (gnutls_log_function);
# ifdef HAVE_GNUTLS3
      gnutls_global_set_audit_log_function (gnutls_audit_log_function);
# endif
      int level = (FIXNUMP (loglevel)
		   ? clip_to_bounds (INT_MIN, XFIXNUM (loglevel), INT_MAX)
		   : NILP (Fnatnump (loglevel)) ? INT_MIN : INT_MAX);
      gnutls_global_set_log_level (level);
      max_log_level = level;
      XPROCESS (proc)->gnutls_log_level = max_log_level;
    }

  GNUTLS_LOG2 (1, max_log_level, "connecting to host:", c_hostname);

  /* Always initialize globals.  */
  global_init = emacs_gnutls_global_init ();
  if (! NILP (Fgnutls_errorp (global_init)))
    return global_init;

  /* Before allocating new credentials, deallocate any credentials
     that PROC might already have.  */
  emacs_gnutls_deinit (proc);

  /* Mark PROC as a GnuTLS process.  */
  XPROCESS (proc)->gnutls_state = NULL;
  XPROCESS (proc)->gnutls_x509_cred = NULL;
  XPROCESS (proc)->gnutls_anon_cred = NULL;
  pset_gnutls_cred_type (XPROCESS (proc), type);
  GNUTLS_INITSTAGE (proc) = GNUTLS_STAGE_EMPTY;

  GNUTLS_LOG (1, max_log_level, "allocating credentials");
  if (EQ (type, Qgnutls_x509pki))
    {
      Lisp_Object verify_flags;
      unsigned int gnutls_verify_flags = GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT;

      GNUTLS_LOG (2, max_log_level, "allocating x509 credentials");
      check_memory_full (gnutls_certificate_allocate_credentials (&x509_cred));
      XPROCESS (proc)->gnutls_x509_cred = x509_cred;

      verify_flags = Fplist_get (proplist, QCverify_flags);
      if (TYPE_RANGED_FIXNUMP (unsigned int, verify_flags))
	{
	  gnutls_verify_flags = XFIXNAT (verify_flags);
	  GNUTLS_LOG (2, max_log_level, "setting verification flags");
	}
      else if (NILP (verify_flags))
	GNUTLS_LOG (2, max_log_level, "using default verification flags");
      else
	GNUTLS_LOG (2, max_log_level, "ignoring invalid verify-flags");

      gnutls_certificate_set_verify_flags (x509_cred, gnutls_verify_flags);
    }
  else /* Qgnutls_anon: */
    {
      GNUTLS_LOG (2, max_log_level, "allocating anon credentials");
      check_memory_full (gnutls_anon_allocate_client_credentials (&anon_cred));
      XPROCESS (proc)->gnutls_anon_cred = anon_cred;
    }

  GNUTLS_INITSTAGE (proc) = GNUTLS_STAGE_CRED_ALLOC;

  if (EQ (type, Qgnutls_x509pki))
    {
      /* TODO: GNUTLS_X509_FMT_DER is also an option.  */
      int file_format = GNUTLS_X509_FMT_PEM;
      Lisp_Object tail;

# ifdef HAVE_GNUTLS_X509_SYSTEM_TRUST
      ret = gnutls_certificate_set_x509_system_trust (x509_cred);
      if (ret < GNUTLS_E_SUCCESS)
	{
	  check_memory_full (ret);
	  GNUTLS_LOG2i (4, max_log_level,
			"setting system trust failed with code ", ret);
	}
# endif

      for (tail = trustfiles; CONSP (tail); tail = XCDR (tail))
	{
	  Lisp_Object trustfile = XCAR (tail);
	  if (STRINGP (trustfile))
	    {
	      GNUTLS_LOG2 (1, max_log_level, "setting the trustfile: ",
			   SSDATA (trustfile));
	      trustfile = ENCODE_FILE (trustfile);
# ifdef WINDOWSNT
	      /* Since GnuTLS doesn't support UTF-8 or UTF-16 encoded
		 file names on Windows, we need to re-encode the file
		 name using the current ANSI codepage.  */
	      trustfile = ansi_encode_filename (trustfile);
# endif
	      ret = gnutls_certificate_set_x509_trust_file
		(x509_cred,
		 SSDATA (trustfile),
		 file_format);

	      if (ret < GNUTLS_E_SUCCESS)
		return gnutls_make_error (ret);
	    }
	  else
	    {
	      emacs_gnutls_deinit (proc);
	      boot_error (p, "Invalid trustfile");
	      return Qnil;
	    }
	}

      for (tail = crlfiles; CONSP (tail); tail = XCDR (tail))
	{
	  Lisp_Object crlfile = XCAR (tail);
	  if (STRINGP (crlfile))
	    {
	      GNUTLS_LOG2 (1, max_log_level, "setting the CRL file: ",
			   SSDATA (crlfile));
	      crlfile = ENCODE_FILE (crlfile);
# ifdef WINDOWSNT
	      crlfile = ansi_encode_filename (crlfile);
# endif
	      ret = gnutls_certificate_set_x509_crl_file
		(x509_cred, SSDATA (crlfile), file_format);

	      if (ret < GNUTLS_E_SUCCESS)
		return gnutls_make_error (ret);
	    }
	  else
	    {
	      emacs_gnutls_deinit (proc);
	      boot_error (p, "Invalid CRL file");
	      return Qnil;
	    }
	}

      for (tail = keylist; CONSP (tail); tail = XCDR (tail))
	{
	  Lisp_Object keyfile = Fcar (XCAR (tail));
	  Lisp_Object certfile = Fcar (Fcdr (XCAR (tail)));
	  if (STRINGP (keyfile) && STRINGP (certfile))
	    {
	      GNUTLS_LOG2 (1, max_log_level, "setting the client key file: ",
			   SSDATA (keyfile));
	      GNUTLS_LOG2 (1, max_log_level, "setting the client cert file: ",
			   SSDATA (certfile));
	      keyfile = ENCODE_FILE (keyfile);
	      certfile = ENCODE_FILE (certfile);
# ifdef WINDOWSNT
	      keyfile = ansi_encode_filename (keyfile);
	      certfile = ansi_encode_filename (certfile);
# endif
	      ret = gnutls_certificate_set_x509_key_file
		(x509_cred, SSDATA (certfile), SSDATA (keyfile), file_format);

	      if (ret < GNUTLS_E_SUCCESS)
		return gnutls_make_error (ret);
	    }
	  else
	    {
	      emacs_gnutls_deinit (proc);
	      boot_error (p, STRINGP (keyfile) ? "Invalid client cert file"
			  : "Invalid client key file");
	      return Qnil;
	    }
	}
    }

  GNUTLS_INITSTAGE (proc) = GNUTLS_STAGE_FILES;
  GNUTLS_LOG (1, max_log_level, "gnutls callbacks");
  GNUTLS_INITSTAGE (proc) = GNUTLS_STAGE_CALLBACKS;

  /* Call gnutls_init here: */

  GNUTLS_LOG (1, max_log_level, "gnutls_init");
  int gnutls_flags = GNUTLS_CLIENT;
# ifdef GNUTLS_NONBLOCK
  if (XPROCESS (proc)->is_non_blocking_client)
    gnutls_flags |= GNUTLS_NONBLOCK;
# endif
  ret = gnutls_init (&state, gnutls_flags);
  XPROCESS (proc)->gnutls_state = state;
  if (ret < GNUTLS_E_SUCCESS)
    return gnutls_make_error (ret);
  GNUTLS_INITSTAGE (proc) = GNUTLS_STAGE_INIT;

  if (STRINGP (priority_string))
    {
      priority_string_ptr = SSDATA (priority_string);
      GNUTLS_LOG2 (1, max_log_level, "got non-default priority string:",
		   priority_string_ptr);
    }
  else
    {
      GNUTLS_LOG2 (1, max_log_level, "using default priority string:",
		   priority_string_ptr);
    }

  GNUTLS_LOG (1, max_log_level, "setting the priority string");
  ret = gnutls_priority_set_direct (state, priority_string_ptr, NULL);
  if (ret < GNUTLS_E_SUCCESS)
    return gnutls_make_error (ret);

  GNUTLS_INITSTAGE (proc) = GNUTLS_STAGE_PRIORITY;

  if (FIXNUMP (prime_bits))
    gnutls_dh_set_prime_bits (state, XUFIXNUM (prime_bits));

  ret = EQ (type, Qgnutls_x509pki)
    ? gnutls_credentials_set (state, GNUTLS_CRD_CERTIFICATE, x509_cred)
    : gnutls_credentials_set (state, GNUTLS_CRD_ANON, anon_cred);
  if (ret < GNUTLS_E_SUCCESS)
    return gnutls_make_error (ret);

  if (!gnutls_ip_address_p (c_hostname))
    {
      ret = gnutls_server_name_set (state, GNUTLS_NAME_DNS, c_hostname,
				    strlen (c_hostname));
      if (ret < GNUTLS_E_SUCCESS)
	return gnutls_make_error (ret);
    }

  XPROCESS (proc)->gnutls_complete_negotiation_p =
    !NILP (Fplist_get (proplist, QCcomplete_negotiation));
  GNUTLS_INITSTAGE (proc) = GNUTLS_STAGE_CRED_SET;
  ret = emacs_gnutls_handshake (XPROCESS (proc));
  if (ret < GNUTLS_E_SUCCESS)
    return gnutls_make_error (ret);

  return gnutls_verify_boot (proc, proplist);
}