[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Patch for client certificate authentication

From: Daniel Berlin <dberlin_at_dberlin.org>
Date: 2002-08-06 05:08:19 CEST

I'll be going away for two days, and i'd appreciate it if some other
people would give this patch a try.

I've tried it against my own apache2+mod_ssl server, and it seems to do
set to require client cert authentication, and it seems to do what it
should (IE fails when the server can't vaidate the cert, succeeds when it
can).

As i'm not asking for a review, just testing, i've left out the changelog.

Please note you'll need the latest CVS neon, and to pass
--disable-neon-version-check to subversion's configure.

Just try to make a bs connection the first time so that it creates the
~/.subversion/certificates file (it creates it at the same time it would
create ~/.subversion/proxies), and the format should be self explanatory.

Any crashes in openssl or neon are likely *not* my fault. I've valgrind'd
this code repeatedly, and can assure it doesn't do anything bad to memory
no matter what i try to do.

Note that your private key password will be cached in the auth file, so
that you don't have to type it in after the initial checkout. Whether we
should do this or not, i'm not sure (the argument comes down to whether
people consider private key passwords on client certs as more private than
normal username/passwords).

If the server can't verify the cert, you'll get an error about no part of
the url being found in the path or something like that. This is a bogus
error message, i'm working on how to make it print a sensible one in this
case.

Index: ./subversion/include/svn_config.h
===================================================================
--- ./subversion/include/svn_config.h
+++ ./subversion/include/svn_config.h 2002-08-01 12:29:10.000000000 -0400
@@ -68,6 +68,25 @@
 svn_error_t *svn_config_read_proxies (svn_config_t **cfgp, apr_pool_t *pool);
 
 
+/* Merge certificates configuration information from all available
+ sources and store it in *CFGP, which is allocated in POOL. That
+ is, first read any system-wide certificate configurations (from a
+ file or from the registry), then merge in personal certificate
+ configurations (again from file or registry).
+
+ Under Unix, or a Unix emulator such as Cygwin, personal config is
+ always located in .subversion/certificates in the user's home
+ directory. Under Windows it may be there, or in the registry; if
+ both are present, the registry is read first and then the file
+ info is merged in. System config information under Windows is
+ found only in the registry.
+
+ If no certificate config information is available, return an empty
+ *CFGP.
+
+*/
+svn_error_t *svn_config_read_certs (svn_config_t **cfgp, apr_pool_t *pool);
+
 /* Read configuration data from FILE (a file or registry path) into
    *CFGP, allocated in POOL.
 
Index: ./subversion/include/svn_error_codes.h
===================================================================
--- ./subversion/include/svn_error_codes.h
+++ ./subversion/include/svn_error_codes.h 2002-08-04 22:44:51.000000000 -0400
@@ -387,8 +387,12 @@
     SVN_ERRDEF (SVN_ERR_RA_PROPS_NOT_FOUND,
                 "RA layer failed to fetch properties")
 
- SVN_ERRDEF (SVN_ERR_RA_ALREADY_EXISTS,
- "RA layer file already exists")
+ SVN_ERRDEF (SVN_ERR_RA_ALREADY_EXISTS,
+ "RA layer file already exists")
+
+ SVN_ERRDEF (SVN_ERR_RA_CERT_PROB,
+ "RA layer had a problem with the certificate")
+
 
   /* End of ra_dav errors */
 
Index: ./subversion/include/svn_ra.h
===================================================================
--- ./subversion/include/svn_ra.h
+++ ./subversion/include/svn_ra.h 2002-08-04 22:06:08.000000000 -0400
@@ -148,7 +148,8 @@
 enum svn_ra_auth_method
 {
   svn_ra_auth_username,
- svn_ra_auth_simple_password
+ svn_ra_auth_simple_password,
+ svn_ra_auth_password
 };
 /* ### someday add other protocols here: PRIVATE_KEY, CERT, etc. */
   
@@ -208,6 +209,31 @@
 
 } svn_ra_simple_password_authenticator_t;
 
+/* A protocol which needs a password only (no username). */
+typedef struct svn_ra_password_authenticator_t
+{
+ /* Get a password from the client. If FORCE_PROMPT is set, then a
+ prompt will be displayed to the user automatically (rather than
+ looking for cached info from the command-line or file.)
+
+ *PASSWORD will not only be returned to the RA layer, but
+ *libsvn_client will also cache them in the AUTH_BATON.
+ */
+
+ svn_error_t *(*get_password) (char **password,
+ void *auth_baton,
+ svn_boolean_t force_prompt,
+ apr_pool_t *pool);
+ /* If any authentication info has been cached in AUTH_BATON
+ (as aresult of calling get_password), ask the client to store it
+ in the working copy.
+
+ If this routine is NULL, that means the client is unable (or
+ unwilling) to store auth data. */
+
+ svn_error_t *(*store_password) (void *auth_baton);
+
+} svn_ra_password_authenticator_t;
 
 
 /* A collection of callbacks implemented by libsvn_client which allows
Index: ./subversion/libsvn_subr/config_file.c
===================================================================
--- ./subversion/libsvn_subr/config_file.c
+++ ./subversion/libsvn_subr/config_file.c 2002-08-04 22:41:12.000000000 -0400
@@ -624,7 +624,6 @@
                                       "closing config file `%s'", path);
         }
     }
-
   /* Ensure that the `proxies' file exists. */
   SVN_ERR (svn_config__user_config_path
            (&path, SVN_CONFIG__USR_PROXY_FILE, pool));
@@ -706,6 +705,71 @@
         }
     }
 
+ /* Ensure that the `certificates' file exists. */
+ SVN_ERR (svn_config__user_config_path
+ (&path, SVN_CONFIG__USR_CERT_FILE, pool));
+
+ if (! path) /* highly unlikely, since a previous call succeeded */
+ return SVN_NO_ERROR;
+
+ err = svn_io_check_path (path, &kind, pool);
+ if (err)
+ return SVN_NO_ERROR;
+
+ if (kind == svn_node_none)
+ {
+ apr_file_t *f;
+ const char *contents =
+ "### This file determines which certificates to use, if\n"
+ "### any, when contacting a remote repository.\n"
+ "###\n"
+ "### The commented-out examples below are intended only to\n"
+ "### demonstrate how to use this file; any resemblance to\n"
+ "### actual servers, living or dead, is entirely\n"
+ "### coincidental.\n"
+ "\n"
+ "### In this section, the URL of the repository you're\n"
+ "### trying to access is matched against the patterns on\n"
+ "### the right. If a match is found, the certificate info is\n"
+ "### taken from the section with the corresponding name.\n"
+ "### PKCS12 certificates only need a certificate entry.\n"
+ "### PEM certificates should have a privatekey entry, unless\n"
+ "### the private key and cert are in the same file.\n"
+ "# [groups]\n"
+ "# group1 = *.collab.net\n"
+ "# othergroup = repository.blarggitywhoomph.com\n"
+ "\n"
+ "### Information for the first group:\n"
+ "# [group1]\n"
+ "# type=pkcs12\n"
+ "# certificate = group1cert.pkcs12\n"
+ "\n"
+ "### Information for the second group:\n"
+ "# [othergroup]\n"
+ "# type=pem\n"
+ "# certificate = othergroupcert.pem\n"
+ "# privatekey = othergroupkey.pem\n"
+ "\n";
+
+ apr_err = apr_file_open (&f, path,
+ (APR_WRITE | APR_CREATE | APR_EXCL),
+ APR_OS_DEFAULT,
+ pool);
+
+ if (! apr_err)
+ {
+ apr_err = apr_file_write_full (f, contents, strlen (contents), NULL);
+ if (apr_err)
+ return svn_error_createf (apr_err, 0, NULL, pool,
+ "writing config file `%s'", path);
+
+ apr_err = apr_file_close (f);
+ if (apr_err)
+ return svn_error_createf (apr_err, 0, NULL, pool,
+ "closing config file `%s'", path);
+ }
+ }
+
   return SVN_NO_ERROR;
 }
 
Index: ./subversion/libsvn_subr/config.c
===================================================================
--- ./subversion/libsvn_subr/config.c
+++ ./subversion/libsvn_subr/config.c 2002-08-01 12:32:40.000000000 -0400
@@ -212,6 +212,37 @@
 }
 
 
+svn_error_t *
+svn_config_read_certs (svn_config_t **cfgp, apr_pool_t *pool)
+{
+ const char *usr_reg_path, *sys_reg_path;
+ const char *usr_cfg_path, *sys_cfg_path;
+
+#ifdef SVN_WIN32
+ sys_reg_path = SVN_REGISTRY_SYS_CONFIG_CERT_PATH;
+ usr_reg_path = SVN_REGISTRY_USR_CONFIG_CERT_PATH;
+#else /* SVN_WIN32 */
+ sys_reg_path = usr_reg_path = NULL;
+#endif /* SVN_WIN32 */
+
+ SVN_ERR (svn_config__sys_config_path (&sys_cfg_path,
+ SVN_CONFIG__USR_CERT_FILE,
+ pool));
+
+ SVN_ERR (svn_config__user_config_path (&usr_cfg_path,
+ SVN_CONFIG__USR_CERT_FILE,
+ pool));
+
+ SVN_ERR (read_all (cfgp,
+ sys_reg_path, usr_reg_path,
+ sys_cfg_path, usr_cfg_path,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
 
 /* Iterate through CFG, passing BATON to CALLBACK for every (SECTION, OPTION)
    pair. Stop if CALLBACK returns TRUE. Allocate from POOL. */
Index: ./subversion/libsvn_subr/config_impl.h
===================================================================
--- ./subversion/libsvn_subr/config_impl.h
+++ ./subversion/libsvn_subr/config_impl.h 2002-08-01 12:25:04.000000000 -0400
@@ -93,6 +93,17 @@
                                SVN_REGISTRY_HKCU \
                                SVN_REGISTRY_PATH \
                                SVN_REGISTRY_CONFIG_PROXY_KEY
+# define SVN_REGISTRY_CONFIG_CERT_KEY "Certificates"
+# define SVN_REGISTRY_SYS_CONFIG_CERT_PATH \
+ SVN_REGISTRY_PREFIX \
+ SVN_REGISTRY_HKLM \
+ SVN_REGISTRY_PATH \
+ SVN_REGISTRY_CONFIG_CERT_KEY
+# define SVN_REGISTRY_USR_CONFIG_CERT_PATH \
+ SVN_REGISTRY_PREFIX \
+ SVN_REGISTRY_HKCU \
+ SVN_REGISTRY_PATH \
+ SVN_REGISTRY_CONFIG_CERT_KEY
 
 #endif /* SVN_WIN32 */
 
@@ -112,6 +123,9 @@
 /* The proxy config file in SVN_CONFIG__DIRECTORY. */
 #define SVN_CONFIG__USR_PROXY_FILE "proxies"
 
+/* The certificate config file in SVN_CONFIG__DIRECTORY. */
+#define SVN_CONFIG__USR_CERT_FILE "certificates"
+
 
 /* Set *PATH_P to the path to config file FNAME in the system
    configuration area, allocated in POOL. If FNAME is NULL, set
Index: ./subversion/libsvn_client/auth.c
===================================================================
--- ./subversion/libsvn_client/auth.c
+++ ./subversion/libsvn_client/auth.c 2002-08-04 23:37:40.000000000 -0400
@@ -217,6 +217,16 @@
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+get_password_only (char **password,
+ void *baton,
+ svn_boolean_t force_prompt,
+ apr_pool_t *pool)
+{
+ SVN_ERR (get_password (password, "Private key", baton, force_prompt, pool));
+
+ return SVN_NO_ERROR;
+}
 
 
 static svn_error_t *
@@ -256,7 +266,6 @@
     return SVN_NO_ERROR;
 }
 
-
 static svn_error_t *
 store_password (const char *password,
                 void *baton)
@@ -333,7 +342,22 @@
         *authenticator = ua;
         break;
       }
-
+ case svn_ra_auth_password:
+ {
+ svn_ra_password_authenticator_t *ua
+ = apr_pcalloc (pool, sizeof (*ua));
+
+ ua->get_password = get_password_only;
+ if (cb->do_store)
+ ua->store_password = store_user_and_pass;
+ else
+ ua->store_password = store_user_and_pass;
+
+ cb->auth_baton->username = NULL;
+ *authenticator = ua;
+ break;
+ }
+
     default:
       {
         return svn_error_create (SVN_ERR_RA_UNKNOWN_AUTH, 0, NULL,
Index: ./subversion/libsvn_ra_dav/ra_dav.h
===================================================================
--- ./subversion/libsvn_ra_dav/ra_dav.h
+++ ./subversion/libsvn_ra_dav/ra_dav.h 2002-08-04 23:56:15.000000000 -0400
@@ -52,6 +52,8 @@
   const svn_ra_callbacks_t *callbacks; /* callbacks to get auth data */
   void *callback_baton;
 
+ const char *cached_password;
+
 } svn_ra_session_t;
 
 
Index: ./subversion/libsvn_ra_dav/session.c
===================================================================
--- ./subversion/libsvn_ra_dav/session.c
+++ ./subversion/libsvn_ra_dav/session.c 2002-08-05 22:45:32.000000000 -0400
@@ -1,4 +1,4 @@
-/*
+y/*
  * session.c : routines for maintaining sessions state (to the DAV server)
  *
  * ====================================================================
@@ -123,10 +123,10 @@
 {
   const char *requested_host; /* the host in the original uri */
 
- const char *proxy_group; /* NULL unless/until we find a host
+ const char *group; /* NULL unless/until we find a host
                                   match, in which case this is set to
                                   the name of the config file section
- where we can find proxy information
+ where we can find information
                                   for this host. */
   apr_pool_t *pool;
 };
@@ -168,7 +168,7 @@
  *
  * VALUE is in the same format as the LIST param of match_in_list().
  * If an element of VALUE matches BATON->requested_host, then set
- * BATON->proxy_group to a copy of NAME allocated in BATON->pool, and
+ * BATON->group to a copy of NAME allocated in BATON->pool, and
  * return false, to end the enumeration.
  *
  * If no match, return true, to continue enumerating.
@@ -181,7 +181,7 @@
 
   if (match_in_list(b->requested_host, value, b->pool))
     {
- b->proxy_group = apr_pstrdup(b->pool, name);
+ b->group = apr_pstrdup(b->pool, name);
       return FALSE;
     }
   else
@@ -229,17 +229,17 @@
 
   /* Search for a proxy pattern specific to this host. */
   gb.requested_host = requested_host;
- gb.proxy_group = NULL;
+ gb.group = NULL;
   gb.pool = pool;
   (void) svn_config_enumerate(cfg, "groups", search_groups, &gb);
 
- if (gb.proxy_group)
+ if (gb.group)
     {
- svn_config_get(cfg, proxy_host, gb.proxy_group, "host", *proxy_host);
- svn_config_get(cfg, &port_str, gb.proxy_group, "port", port_str);
- svn_config_get(cfg, proxy_username, gb.proxy_group, "username",
+ svn_config_get(cfg, proxy_host, gb.group, "host", *proxy_host);
+ svn_config_get(cfg, &port_str, gb.group, "port", port_str);
+ svn_config_get(cfg, proxy_username, gb.group, "username",
                      *proxy_username);
- svn_config_get(cfg, proxy_password, gb.proxy_group, "password",
+ svn_config_get(cfg, proxy_password, gb.group, "password",
                      *proxy_password);
     }
 
@@ -260,6 +260,75 @@
   const char *password; /* Cannot be NULL, but "" is okay. */
 };
 
+/* This is the callback function that is used to get a password for
+ the user's private key. */
+static int ssl_provide_cert_pass (void *userdata, char *pwbuf, size_t len)
+{
+ void *a, *auth_baton;
+ char *pword;
+ svn_ra_password_authenticator_t *authenticator = NULL;
+ svn_ra_session_t *ras = userdata;
+ if (ras->cached_password)
+ {
+ apr_cpystrn (pwbuf, ras->cached_password, len);
+ return 0;
+ }
+ ras->callbacks->get_authenticator (&a, &auth_baton,
+ svn_ra_auth_password,
+ ras->callback_baton, ras->pool);
+ authenticator = (svn_ra_password_authenticator_t *) a;
+ authenticator->get_password (&pword,
+ auth_baton,
+ FALSE,
+ ras->pool);
+ ras->cached_password = apr_pstrdup (ras->pool, pword);
+ apr_cpystrn (pwbuf, pword, len);
+ return 0;
+}
+
+static svn_error_t *get_cert (void *userdata, ne_session *sess,
+ const ne_ssl_dname *server)
+{
+ ne_uri uri;
+ const char *cert_fn;
+ const char *cert_type;
+ const char *priv_fn;
+ svn_config_t *cfg;
+ svn_ra_session_t *ras = (svn_ra_session_t *)userdata;
+ struct search_groups_baton gb;
+
+ if (svn_config_read_certs(&cfg, ras->pool) != NULL)
+ return;
+ ne_fill_server_uri (sess, &uri);
+ gb.requested_host = uri.host;
+ gb.group = NULL;
+ gb.pool = ras->pool;
+ (void) svn_config_enumerate(cfg, "groups", search_groups, &gb);
+ if (gb.group)
+ {
+ svn_config_get (cfg, &cert_type, gb.group, "type", NULL);
+ if (cert_type == NULL)
+ return svn_error_createf (SVN_ERR_RA_CERT_PROB, 0, NULL, ras->pool,
+ "Certificate for %s must have type in config file", gb.group);
+ svn_config_get (cfg, &cert_fn, gb.group, "certificate", NULL);
+ if (strcasecmp (cert_type, "pem") == 0)
+ svn_config_get (cfg, &priv_fn, gb.group, "privatekey", NULL);
+ }
+ if (cert_fn != NULL)
+ {
+ int code;
+ if (strcasecmp (cert_type, "pkcs12") == 0)
+ code = ne_ssl_load_pkcs12 (sess, cert_fn);
+ else if (strcasecmp (cert_type, "pem") == 0)
+ code = ne_ssl_load_pem (sess, cert_fn, priv_fn);
+ else
+ return svn_error_createf (SVN_ERR_RA_CERT_PROB, 0, NULL, ras->pool,
+ "Certificate type for %s is invalid", gb.group);
+ if (code != NE_OK)
+ return svn_ra_dav__convert_error (sess, "Trying to load certificate", code, ras->pool);
+ }
+ return SVN_NO_ERROR;
+}
 
 /* An `ne_request_auth' callback, see ne_auth.h. USERDATA is a
  * `struct proxy_auth_baton *'.
@@ -454,15 +523,30 @@
   ras->sess2 = sess2;
   ras->callbacks = callbacks;
   ras->callback_baton = callback_baton;
+ ras->cached_password = NULL;
 
   /* note that ras->username and ras->password are still NULL at this
      point. */
 
-
+
   /* Register an authentication 'pull' callback with the neon sessions */
   ne_set_server_auth(sess, request_auth, ras);
   ne_set_server_auth(sess2, request_auth, ras);
 
+ /* Register necessary callbacks for key passwords, and get the
+ client certificates we will be using. */
+ if (is_ssl_session)
+ {
+ svn_error_t *err;
+ ne_ssl_keypw_prompt (sess, ssl_provide_cert_pass, ras);
+ ne_ssl_keypw_prompt (sess2, ssl_provide_cert_pass, ras);
+ err = get_cert (ras, sess, NULL);
+ if (err != SVN_NO_ERROR)
+ return err;
+ err = get_cert (ras, sess2, NULL);
+ if (err != SVN_NO_ERROR)
+ return err;
+ }
 
   *session_baton = ras;
 
 
--Dan

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Tue Aug 6 05:08:51 2002

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.