ssl_client_cert_pw_providers.c revision 362181
1/*
2 * ssl_client_cert_pw_providers.c: providers for
3 * SVN_AUTH_CRED_SSL_CLIENT_CERT_PW
4 *
5 * ====================================================================
6 *    Licensed to the Apache Software Foundation (ASF) under one
7 *    or more contributor license agreements.  See the NOTICE file
8 *    distributed with this work for additional information
9 *    regarding copyright ownership.  The ASF licenses this file
10 *    to you under the Apache License, Version 2.0 (the
11 *    "License"); you may not use this file except in compliance
12 *    with the License.  You may obtain a copy of the License at
13 *
14 *      http://www.apache.org/licenses/LICENSE-2.0
15 *
16 *    Unless required by applicable law or agreed to in writing,
17 *    software distributed under the License is distributed on an
18 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 *    KIND, either express or implied.  See the License for the
20 *    specific language governing permissions and limitations
21 *    under the License.
22 * ====================================================================
23 */
24
25
26#include <apr_pools.h>
27
28#include "svn_hash.h"
29#include "svn_auth.h"
30#include "svn_error.h"
31#include "svn_config.h"
32#include "svn_string.h"
33
34#include "private/svn_auth_private.h"
35
36#include "svn_private_config.h"
37
38/*-----------------------------------------------------------------------*/
39/* File password provider                                                */
40/*-----------------------------------------------------------------------*/
41
42/* Baton type for the ssl client cert passphrase provider. */
43typedef struct ssl_client_cert_pw_file_provider_baton_t
44{
45  svn_auth_plaintext_passphrase_prompt_func_t plaintext_passphrase_prompt_func;
46  void *prompt_baton;
47  /* We cache the user's answer to the plaintext prompt, keyed
48     by realm, in case we'll be called multiple times for the
49     same realm.  So: keys are 'const char *' realm strings, and
50     values are 'svn_boolean_t *'. */
51  apr_hash_t *plaintext_answers;
52} ssl_client_cert_pw_file_provider_baton_t;
53
54/* The client cert password provider only deals with a password and
55   realm (the client cert filename), there is no username.  The gnome
56   keyring backend based on libsecret requires a non-NULL username so
57   we have to invent one.  An empty string is acceptable and doesn't
58   change the value stored by the kwallet backend. */
59#define DUMMY_USERNAME ""
60
61/* This implements the svn_auth__password_get_t interface.
62   Set **PASSPHRASE to the plaintext passphrase retrieved from CREDS;
63   ignore other parameters. */
64svn_error_t *
65svn_auth__ssl_client_cert_pw_get(svn_boolean_t *done,
66                                 const char **passphrase,
67                                 apr_hash_t *creds,
68                                 const char *realmstring,
69                                 const char *username,
70                                 apr_hash_t *parameters,
71                                 svn_boolean_t non_interactive,
72                                 apr_pool_t *pool)
73{
74  svn_string_t *str;
75  str = svn_hash_gets(creds, SVN_CONFIG_AUTHN_PASSPHRASE_KEY);
76  if (str && str->data)
77    {
78      *passphrase = str->data;
79      *done = TRUE;
80      return SVN_NO_ERROR;
81    }
82  *done = FALSE;
83  return SVN_NO_ERROR;
84}
85
86/* This implements the svn_auth__password_set_t interface.
87   Store PASSPHRASE in CREDS; ignore other parameters. */
88svn_error_t *
89svn_auth__ssl_client_cert_pw_set(svn_boolean_t *done,
90                                 apr_hash_t *creds,
91                                 const char *realmstring,
92                                 const char *username,
93                                 const char *passphrase,
94                                 apr_hash_t *parameters,
95                                 svn_boolean_t non_interactive,
96                                 apr_pool_t *pool)
97{
98  svn_hash_sets(creds, SVN_CONFIG_AUTHN_PASSPHRASE_KEY,
99                svn_string_create(passphrase, pool));
100  *done = TRUE;
101  return SVN_NO_ERROR;
102}
103
104svn_error_t *
105svn_auth__ssl_client_cert_pw_cache_get(void **credentials_p,
106                                       void **iter_baton,
107                                       void *provider_baton,
108                                       apr_hash_t *parameters,
109                                       const char *realmstring,
110                                       svn_auth__password_get_t passphrase_get,
111                                       const char *passtype,
112                                       apr_pool_t *pool)
113{
114  svn_config_t *cfg = svn_hash_gets(parameters,
115                                    SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS);
116  const char *server_group = svn_hash_gets(parameters,
117                                           SVN_AUTH_PARAM_SERVER_GROUP);
118  svn_boolean_t non_interactive = svn_hash_gets(parameters,
119                                                SVN_AUTH_PARAM_NON_INTERACTIVE)
120      != NULL;
121  const char *password =
122    svn_config_get_server_setting(cfg, server_group,
123                                  SVN_CONFIG_OPTION_SSL_CLIENT_CERT_PASSWORD,
124                                  NULL);
125  if (! password)
126    {
127      svn_error_t *err;
128      apr_hash_t *creds_hash = NULL;
129      const char *config_dir = svn_hash_gets(parameters,
130                                             SVN_AUTH_PARAM_CONFIG_DIR);
131
132      /* Try to load passphrase from the auth/ cache. */
133      err = svn_config_read_auth_data(&creds_hash,
134                                      SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
135                                      realmstring, config_dir, pool);
136      svn_error_clear(err);
137      if (! err && creds_hash)
138        {
139          svn_boolean_t done;
140
141          SVN_ERR(passphrase_get(&done, &password, creds_hash, realmstring,
142                                 DUMMY_USERNAME, parameters, non_interactive,
143                                 pool));
144          if (!done)
145            password = NULL;
146        }
147    }
148
149  if (password)
150    {
151      svn_auth_cred_ssl_client_cert_pw_t *cred
152        = apr_palloc(pool, sizeof(*cred));
153      cred->password = password;
154      cred->may_save = FALSE;
155      *credentials_p = cred;
156    }
157  else *credentials_p = NULL;
158  *iter_baton = NULL;
159  return SVN_NO_ERROR;
160}
161
162
163svn_error_t *
164svn_auth__ssl_client_cert_pw_cache_set(svn_boolean_t *saved,
165                                       void *credentials,
166                                       void *provider_baton,
167                                       apr_hash_t *parameters,
168                                       const char *realmstring,
169                                       svn_auth__password_set_t passphrase_set,
170                                       const char *passtype,
171                                       apr_pool_t *pool)
172{
173  svn_auth_cred_ssl_client_cert_pw_t *creds = credentials;
174  apr_hash_t *creds_hash = NULL;
175  const char *config_dir;
176  svn_error_t *err;
177  svn_boolean_t dont_store_passphrase =
178    svn_hash_gets(parameters, SVN_AUTH_PARAM_DONT_STORE_SSL_CLIENT_CERT_PP)
179    != NULL;
180  svn_boolean_t non_interactive =
181      svn_hash_gets(parameters, SVN_AUTH_PARAM_NON_INTERACTIVE) != NULL;
182  svn_boolean_t no_auth_cache =
183    (! creds->may_save)
184    || (svn_hash_gets(parameters, SVN_AUTH_PARAM_NO_AUTH_CACHE) != NULL);
185
186  *saved = FALSE;
187
188  if (no_auth_cache)
189    return SVN_NO_ERROR;
190
191  config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR);
192  creds_hash = apr_hash_make(pool);
193
194  /* Don't store passphrase in any form if the user has told
195     us not to do so. */
196  if (! dont_store_passphrase)
197    {
198      svn_boolean_t may_save_passphrase = FALSE;
199
200      /* If the passphrase is going to be stored encrypted, go right
201         ahead and store it to disk. Else determine whether saving
202         in plaintext is OK. */
203      if (strcmp(passtype, SVN_AUTH__WINCRYPT_PASSWORD_TYPE) == 0
204          || strcmp(passtype, SVN_AUTH__KWALLET_PASSWORD_TYPE) == 0
205          || strcmp(passtype, SVN_AUTH__GNOME_KEYRING_PASSWORD_TYPE) == 0
206          || strcmp(passtype, SVN_AUTH__KEYCHAIN_PASSWORD_TYPE) == 0)
207        {
208          may_save_passphrase = TRUE;
209        }
210      else
211        {
212#ifdef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
213          may_save_passphrase = FALSE;
214#else
215          const char *store_ssl_client_cert_pp_plaintext =
216            svn_hash_gets(parameters,
217                          SVN_AUTH_PARAM_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT);
218          ssl_client_cert_pw_file_provider_baton_t *b =
219            (ssl_client_cert_pw_file_provider_baton_t *)provider_baton;
220
221          if (svn_cstring_casecmp(store_ssl_client_cert_pp_plaintext,
222                                  SVN_CONFIG_ASK) == 0)
223            {
224              if (non_interactive)
225                {
226                  /* In non-interactive mode, the default behaviour is
227                     to not store the passphrase */
228                  may_save_passphrase = FALSE;
229                }
230              else if (b->plaintext_passphrase_prompt_func)
231                {
232                  /* We're interactive, and the client provided a
233                     prompt callback.  So we can ask the user.
234                     Check for a cached answer before prompting.
235
236                     This is a pointer-to-boolean, rather than just a
237                     boolean, because we must distinguish between
238                     "cached answer is no" and "no answer has been
239                     cached yet". */
240                  svn_boolean_t *cached_answer =
241                    svn_hash_gets(b->plaintext_answers, realmstring);
242
243                  if (cached_answer != NULL)
244                    {
245                      may_save_passphrase = *cached_answer;
246                    }
247                  else
248                    {
249                      apr_pool_t *cached_answer_pool;
250
251                      /* Nothing cached for this realm, prompt the user. */
252                      SVN_ERR((*b->plaintext_passphrase_prompt_func)(
253                                &may_save_passphrase,
254                                realmstring,
255                                b->prompt_baton,
256                                pool));
257
258                      /* Cache the user's answer in case we're called again
259                       * for the same realm.
260                       *
261                       * We allocate the answer cache in the hash table's pool
262                       * to make sure that is has the same life time as the
263                       * hash table itself. This means that the answer will
264                       * survive across RA sessions -- which is important,
265                       * because otherwise we'd prompt users once per RA session.
266                       */
267                      cached_answer_pool = apr_hash_pool_get(b->plaintext_answers);
268                      cached_answer = apr_palloc(cached_answer_pool,
269                                                 sizeof(*cached_answer));
270                      *cached_answer = may_save_passphrase;
271                      svn_hash_sets(b->plaintext_answers, realmstring,
272                                    cached_answer);
273                    }
274                }
275              else
276                {
277                  may_save_passphrase = FALSE;
278                }
279            }
280          else if (svn_cstring_casecmp(store_ssl_client_cert_pp_plaintext,
281                                       SVN_CONFIG_FALSE) == 0)
282            {
283              may_save_passphrase = FALSE;
284            }
285          else if (svn_cstring_casecmp(store_ssl_client_cert_pp_plaintext,
286                                       SVN_CONFIG_TRUE) == 0)
287            {
288              may_save_passphrase = TRUE;
289            }
290          else
291            {
292              return svn_error_createf
293                (SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL,
294                 _("Config error: invalid value '%s' for option '%s'"),
295                store_ssl_client_cert_pp_plaintext,
296                SVN_AUTH_PARAM_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT);
297            }
298#endif
299        }
300
301      if (may_save_passphrase)
302        {
303          SVN_ERR(passphrase_set(saved, creds_hash, realmstring,
304                                 DUMMY_USERNAME, creds->password, parameters,
305                                 non_interactive, pool));
306
307          if (*saved && passtype)
308            {
309              svn_hash_sets(creds_hash, SVN_CONFIG_AUTHN_PASSTYPE_KEY,
310                            svn_string_create(passtype, pool));
311            }
312
313          /* Save credentials to disk. */
314          err = svn_config_write_auth_data(creds_hash,
315                                           SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
316                                           realmstring, config_dir, pool);
317          svn_error_clear(err);
318          *saved = ! err;
319        }
320    }
321
322  return SVN_NO_ERROR;
323}
324
325
326/* This implements the svn_auth_provider_t.first_credentials API.
327   It gets cached (unencrypted) credentials from the ssl client cert
328   password provider's cache. */
329static svn_error_t *
330ssl_client_cert_pw_file_first_credentials(void **credentials_p,
331                                          void **iter_baton,
332                                          void *provider_baton,
333                                          apr_hash_t *parameters,
334                                          const char *realmstring,
335                                          apr_pool_t *pool)
336{
337  return svn_auth__ssl_client_cert_pw_cache_get(credentials_p, iter_baton,
338                                                provider_baton, parameters,
339                                                realmstring,
340                                                svn_auth__ssl_client_cert_pw_get,
341                                                SVN_AUTH__SIMPLE_PASSWORD_TYPE,
342                                                pool);
343}
344
345
346/* This implements the svn_auth_provider_t.save_credentials API.
347   It saves the credentials unencrypted. */
348static svn_error_t *
349ssl_client_cert_pw_file_save_credentials(svn_boolean_t *saved,
350                                         void *credentials,
351                                         void *provider_baton,
352                                         apr_hash_t *parameters,
353                                         const char *realmstring,
354                                         apr_pool_t *pool)
355{
356  return svn_auth__ssl_client_cert_pw_cache_set(saved, credentials,
357                                                provider_baton,
358                                                parameters,
359                                                realmstring,
360                                                svn_auth__ssl_client_cert_pw_set,
361                                                SVN_AUTH__SIMPLE_PASSWORD_TYPE,
362                                                pool);
363}
364
365
366static const svn_auth_provider_t ssl_client_cert_pw_file_provider = {
367  SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
368  ssl_client_cert_pw_file_first_credentials,
369  NULL,
370  ssl_client_cert_pw_file_save_credentials
371};
372
373
374/*** Public API to SSL file providers. ***/
375void
376svn_auth_get_ssl_client_cert_pw_file_provider2
377  (svn_auth_provider_object_t **provider,
378   svn_auth_plaintext_passphrase_prompt_func_t plaintext_passphrase_prompt_func,
379   void *prompt_baton,
380   apr_pool_t *pool)
381{
382  svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
383  ssl_client_cert_pw_file_provider_baton_t *pb = apr_pcalloc(pool,
384                                                             sizeof(*pb));
385
386  pb->plaintext_passphrase_prompt_func = plaintext_passphrase_prompt_func;
387  pb->prompt_baton = prompt_baton;
388  pb->plaintext_answers = apr_hash_make(pool);
389
390  po->vtable = &ssl_client_cert_pw_file_provider;
391  po->provider_baton = pb;
392  *provider = po;
393}
394
395
396/*-----------------------------------------------------------------------*/
397/* Prompt provider                                                       */
398/*-----------------------------------------------------------------------*/
399
400/* Baton type for client passphrase prompting.
401   There is no iteration baton type. */
402typedef struct ssl_client_cert_pw_prompt_provider_baton_t
403{
404  svn_auth_ssl_client_cert_pw_prompt_func_t prompt_func;
405  void *prompt_baton;
406
407  /* how many times to re-prompt after the first one fails */
408  int retry_limit;
409} ssl_client_cert_pw_prompt_provider_baton_t;
410
411/* Iteration baton. */
412typedef struct ssl_client_cert_pw_prompt_iter_baton_t
413{
414  /* The original provider baton */
415  ssl_client_cert_pw_prompt_provider_baton_t *pb;
416
417  /* The original realmstring */
418  const char *realmstring;
419
420  /* how many times we've reprompted */
421  int retries;
422} ssl_client_cert_pw_prompt_iter_baton_t;
423
424
425static svn_error_t *
426ssl_client_cert_pw_prompt_first_cred(void **credentials_p,
427                                     void **iter_baton,
428                                     void *provider_baton,
429                                     apr_hash_t *parameters,
430                                     const char *realmstring,
431                                     apr_pool_t *pool)
432{
433  ssl_client_cert_pw_prompt_provider_baton_t *pb = provider_baton;
434  ssl_client_cert_pw_prompt_iter_baton_t *ib =
435    apr_pcalloc(pool, sizeof(*ib));
436  const char *no_auth_cache = svn_hash_gets(parameters,
437                                            SVN_AUTH_PARAM_NO_AUTH_CACHE);
438
439  SVN_ERR(pb->prompt_func((svn_auth_cred_ssl_client_cert_pw_t **)
440                          credentials_p, pb->prompt_baton, realmstring,
441                          ! no_auth_cache, pool));
442
443  ib->pb = pb;
444  ib->realmstring = apr_pstrdup(pool, realmstring);
445  ib->retries = 0;
446  *iter_baton = ib;
447
448  return SVN_NO_ERROR;
449}
450
451
452static svn_error_t *
453ssl_client_cert_pw_prompt_next_cred(void **credentials_p,
454                                    void *iter_baton,
455                                    void *provider_baton,
456                                    apr_hash_t *parameters,
457                                    const char *realmstring,
458                                    apr_pool_t *pool)
459{
460  ssl_client_cert_pw_prompt_iter_baton_t *ib = iter_baton;
461  const char *no_auth_cache = svn_hash_gets(parameters,
462                                            SVN_AUTH_PARAM_NO_AUTH_CACHE);
463
464  if ((ib->pb->retry_limit >= 0) && (ib->retries >= ib->pb->retry_limit))
465    {
466      /* give up, go on to next provider. */
467      *credentials_p = NULL;
468      return SVN_NO_ERROR;
469    }
470  ib->retries++;
471
472  return ib->pb->prompt_func((svn_auth_cred_ssl_client_cert_pw_t **)
473                             credentials_p, ib->pb->prompt_baton,
474                             ib->realmstring, ! no_auth_cache, pool);
475}
476
477
478static const svn_auth_provider_t client_cert_pw_prompt_provider = {
479  SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
480  ssl_client_cert_pw_prompt_first_cred,
481  ssl_client_cert_pw_prompt_next_cred,
482  NULL
483};
484
485
486void svn_auth_get_ssl_client_cert_pw_prompt_provider
487  (svn_auth_provider_object_t **provider,
488   svn_auth_ssl_client_cert_pw_prompt_func_t prompt_func,
489   void *prompt_baton,
490   int retry_limit,
491   apr_pool_t *pool)
492{
493  svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
494  ssl_client_cert_pw_prompt_provider_baton_t *pb =
495    apr_palloc(pool, sizeof(*pb));
496
497  pb->prompt_func = prompt_func;
498  pb->prompt_baton = prompt_baton;
499  pb->retry_limit = retry_limit;
500
501  po->vtable = &client_cert_pw_prompt_provider;
502  po->provider_baton = pb;
503  *provider = po;
504}
505