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. */
43 typedef 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. */
64 svn_error_t *
svn_auth__ssl_client_cert_pw_get(svn_boolean_t * done,const char ** passphrase,apr_hash_t * creds,const char * realmstring,const char * username,apr_hash_t * parameters,svn_boolean_t non_interactive,apr_pool_t * pool)65 svn_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. */
88 svn_error_t *
svn_auth__ssl_client_cert_pw_set(svn_boolean_t * done,apr_hash_t * creds,const char * realmstring,const char * username,const char * passphrase,apr_hash_t * parameters,svn_boolean_t non_interactive,apr_pool_t * pool)89 svn_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 
104 svn_error_t *
svn_auth__ssl_client_cert_pw_cache_get(void ** credentials_p,void ** iter_baton,void * provider_baton,apr_hash_t * parameters,const char * realmstring,svn_auth__password_get_t passphrase_get,const char * passtype,apr_pool_t * pool)105 svn_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 
163 svn_error_t *
svn_auth__ssl_client_cert_pw_cache_set(svn_boolean_t * saved,void * credentials,void * provider_baton,apr_hash_t * parameters,const char * realmstring,svn_auth__password_set_t passphrase_set,const char * passtype,apr_pool_t * pool)164 svn_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. */
329 static svn_error_t *
ssl_client_cert_pw_file_first_credentials(void ** credentials_p,void ** iter_baton,void * provider_baton,apr_hash_t * parameters,const char * realmstring,apr_pool_t * pool)330 ssl_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. */
348 static svn_error_t *
ssl_client_cert_pw_file_save_credentials(svn_boolean_t * saved,void * credentials,void * provider_baton,apr_hash_t * parameters,const char * realmstring,apr_pool_t * pool)349 ssl_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 
366 static 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. ***/
375 void
svn_auth_get_ssl_client_cert_pw_file_provider2(svn_auth_provider_object_t ** provider,svn_auth_plaintext_passphrase_prompt_func_t plaintext_passphrase_prompt_func,void * prompt_baton,apr_pool_t * pool)376 svn_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. */
402 typedef 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. */
412 typedef 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 
425 static svn_error_t *
ssl_client_cert_pw_prompt_first_cred(void ** credentials_p,void ** iter_baton,void * provider_baton,apr_hash_t * parameters,const char * realmstring,apr_pool_t * pool)426 ssl_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 
452 static svn_error_t *
ssl_client_cert_pw_prompt_next_cred(void ** credentials_p,void * iter_baton,void * provider_baton,apr_hash_t * parameters,const char * realmstring,apr_pool_t * pool)453 ssl_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 
478 static 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 
svn_auth_get_ssl_client_cert_pw_prompt_provider(svn_auth_provider_object_t ** provider,svn_auth_ssl_client_cert_pw_prompt_func_t prompt_func,void * prompt_baton,int retry_limit,apr_pool_t * pool)486 void 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