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