win32_crypto.c revision 362181
1/*
2 * win32_crypto.c: win32 providers for SVN_AUTH_*
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* prevent "empty compilation unit" warning on e.g. UNIX */
25typedef int win32_crypto__dummy;
26
27/* ==================================================================== */
28
29#if defined(WIN32) && !defined(__MINGW32__)
30
31/*** Includes. ***/
32
33#include <apr_pools.h>
34#include <apr_base64.h>
35#include "svn_auth.h"
36#include "svn_error.h"
37#include "svn_hash.h"
38#include "svn_utf.h"
39#include "svn_config.h"
40#include "svn_user.h"
41#include "svn_base64.h"
42
43#include "auth.h"
44#include "private/svn_auth_private.h"
45
46#include "svn_private_config.h"
47
48#include <wincrypt.h>
49
50
51/* The description string that's combined with unencrypted data by the
52   Windows CryptoAPI. Used during decryption to verify that the
53   encrypted data were valid. */
54static const WCHAR description[] = L"auth_svn.simple.wincrypt";
55
56
57/* Return a copy of ORIG, encrypted using the Windows CryptoAPI and
58   allocated from POOL. */
59const svn_string_t *
60encrypt_data(const svn_string_t *orig,
61             apr_pool_t *pool)
62{
63  DATA_BLOB blobin;
64  DATA_BLOB blobout;
65  const svn_string_t *crypted = NULL;
66
67  blobin.cbData = orig->len;
68  blobin.pbData = (BYTE *)orig->data;
69  if (CryptProtectData(&blobin, description, NULL, NULL, NULL,
70                       CRYPTPROTECT_UI_FORBIDDEN, &blobout))
71    {
72      crypted = svn_string_ncreate((const char *)blobout.pbData,
73                                   blobout.cbData, pool);
74      LocalFree(blobout.pbData);
75    }
76  return crypted;
77}
78
79/* Return a copy of CRYPTED, decrypted using the Windows CryptoAPI and
80   allocated from POOL. */
81const svn_string_t *
82decrypt_data(const svn_string_t *crypted,
83             apr_pool_t *pool)
84{
85  DATA_BLOB blobin;
86  DATA_BLOB blobout;
87  LPWSTR descr;
88  const svn_string_t *orig = NULL;
89
90  blobin.cbData = crypted->len;
91  blobin.pbData = (BYTE *)crypted->data;
92  if (CryptUnprotectData(&blobin, &descr, NULL, NULL, NULL,
93                         CRYPTPROTECT_UI_FORBIDDEN, &blobout))
94    {
95      if (0 == lstrcmpW(descr, description))
96        orig = svn_string_ncreate((const char *)blobout.pbData,
97                                  blobout.cbData, pool);
98      LocalFree(blobout.pbData);
99      LocalFree(descr);
100    }
101  return orig;
102}
103
104
105/*-----------------------------------------------------------------------*/
106/* Windows simple provider, encrypts the password on Win2k and later.    */
107/*-----------------------------------------------------------------------*/
108
109/* Implementation of svn_auth__password_set_t that encrypts
110   the incoming password using the Windows CryptoAPI. */
111static svn_error_t *
112windows_password_encrypter(svn_boolean_t *done,
113                           apr_hash_t *creds,
114                           const char *realmstring,
115                           const char *username,
116                           const char *in,
117                           apr_hash_t *parameters,
118                           svn_boolean_t non_interactive,
119                           apr_pool_t *pool)
120{
121  const svn_string_t *coded;
122
123  coded = encrypt_data(svn_string_create(in, pool), pool);
124  if (coded)
125    {
126      coded = svn_base64_encode_string2(coded, FALSE, pool);
127      SVN_ERR(svn_auth__simple_password_set(done, creds, realmstring, username,
128                                            coded->data, parameters,
129                                            non_interactive, pool));
130    }
131
132  return SVN_NO_ERROR;
133}
134
135/* Implementation of svn_auth__password_get_t that decrypts
136   the incoming password using the Windows CryptoAPI and verifies its
137   validity. */
138static svn_error_t *
139windows_password_decrypter(svn_boolean_t *done,
140                           const char **out,
141                           apr_hash_t *creds,
142                           const char *realmstring,
143                           const char *username,
144                           apr_hash_t *parameters,
145                           svn_boolean_t non_interactive,
146                           apr_pool_t *pool)
147{
148  const svn_string_t *orig;
149  const char *in;
150
151  SVN_ERR(svn_auth__simple_password_get(done, &in, creds, realmstring, username,
152                                        parameters, non_interactive, pool));
153  if (!*done)
154    return SVN_NO_ERROR;
155
156  orig = svn_base64_decode_string(svn_string_create(in, pool), pool);
157  orig = decrypt_data(orig, pool);
158  if (orig)
159    {
160      *out = orig->data;
161      *done = TRUE;
162    }
163  else
164    {
165      *done = FALSE;
166    }
167  return SVN_NO_ERROR;
168}
169
170/* Get cached encrypted credentials from the simple provider's cache. */
171static svn_error_t *
172windows_simple_first_creds(void **credentials,
173                           void **iter_baton,
174                           void *provider_baton,
175                           apr_hash_t *parameters,
176                           const char *realmstring,
177                           apr_pool_t *pool)
178{
179  return svn_auth__simple_creds_cache_get(credentials,
180                                          iter_baton,
181                                          provider_baton,
182                                          parameters,
183                                          realmstring,
184                                          windows_password_decrypter,
185                                          SVN_AUTH__WINCRYPT_PASSWORD_TYPE,
186                                          pool);
187}
188
189/* Save encrypted credentials to the simple provider's cache. */
190static svn_error_t *
191windows_simple_save_creds(svn_boolean_t *saved,
192                          void *credentials,
193                          void *provider_baton,
194                          apr_hash_t *parameters,
195                          const char *realmstring,
196                          apr_pool_t *pool)
197{
198  return svn_auth__simple_creds_cache_set(saved, credentials,
199                                          provider_baton,
200                                          parameters,
201                                          realmstring,
202                                          windows_password_encrypter,
203                                          SVN_AUTH__WINCRYPT_PASSWORD_TYPE,
204                                          pool);
205}
206
207static const svn_auth_provider_t windows_simple_provider = {
208  SVN_AUTH_CRED_SIMPLE,
209  windows_simple_first_creds,
210  NULL,
211  windows_simple_save_creds
212};
213
214
215/* Public API */
216void
217svn_auth__get_windows_simple_provider(svn_auth_provider_object_t **provider,
218                                     apr_pool_t *pool)
219{
220  svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
221
222  po->vtable = &windows_simple_provider;
223  *provider = po;
224}
225
226
227/*-----------------------------------------------------------------------*/
228/* Windows SSL server trust provider, validates ssl certificate using    */
229/* CryptoApi.                                                            */
230/*-----------------------------------------------------------------------*/
231
232/* Implementation of svn_auth__password_set_t that encrypts
233   the incoming password using the Windows CryptoAPI. */
234static svn_error_t *
235windows_ssl_client_cert_pw_encrypter(svn_boolean_t *done,
236                                     apr_hash_t *creds,
237                                     const char *realmstring,
238                                     const char *username,
239                                     const char *in,
240                                     apr_hash_t *parameters,
241                                     svn_boolean_t non_interactive,
242                                     apr_pool_t *pool)
243{
244  const svn_string_t *coded;
245
246  coded = encrypt_data(svn_string_create(in, pool), pool);
247  if (coded)
248    {
249      coded = svn_base64_encode_string2(coded, FALSE, pool);
250      SVN_ERR(svn_auth__ssl_client_cert_pw_set(done, creds, realmstring,
251                                               username, coded->data,
252                                               parameters, non_interactive,
253                                               pool));
254    }
255
256  return SVN_NO_ERROR;
257}
258
259/* Implementation of svn_auth__password_get_t that decrypts
260   the incoming password using the Windows CryptoAPI and verifies its
261   validity. */
262static svn_error_t *
263windows_ssl_client_cert_pw_decrypter(svn_boolean_t *done,
264                                     const char **out,
265                                     apr_hash_t *creds,
266                                     const char *realmstring,
267                                     const char *username,
268                                     apr_hash_t *parameters,
269                                     svn_boolean_t non_interactive,
270                                     apr_pool_t *pool)
271{
272  const svn_string_t *orig;
273  const char *in;
274
275  SVN_ERR(svn_auth__ssl_client_cert_pw_get(done, &in, creds, realmstring,
276                                           username, parameters,
277                                           non_interactive, pool));
278  if (!*done)
279    return SVN_NO_ERROR;
280
281  orig = svn_base64_decode_string(svn_string_create(in, pool), pool);
282  orig = decrypt_data(orig, pool);
283  if (orig)
284    {
285      *out = orig->data;
286      *done = TRUE;
287    }
288  else
289    {
290      *done = FALSE;
291    }
292  return SVN_NO_ERROR;
293}
294
295/* Get cached encrypted credentials from the simple provider's cache. */
296static svn_error_t *
297windows_ssl_client_cert_pw_first_creds(void **credentials,
298                                       void **iter_baton,
299                                       void *provider_baton,
300                                       apr_hash_t *parameters,
301                                       const char *realmstring,
302                                       apr_pool_t *pool)
303{
304  return svn_auth__ssl_client_cert_pw_cache_get(
305             credentials, iter_baton, provider_baton, parameters, realmstring,
306             windows_ssl_client_cert_pw_decrypter,
307             SVN_AUTH__WINCRYPT_PASSWORD_TYPE, pool);
308}
309
310/* Save encrypted credentials to the simple provider's cache. */
311static svn_error_t *
312windows_ssl_client_cert_pw_save_creds(svn_boolean_t *saved,
313                                      void *credentials,
314                                      void *provider_baton,
315                                      apr_hash_t *parameters,
316                                      const char *realmstring,
317                                      apr_pool_t *pool)
318{
319  return svn_auth__ssl_client_cert_pw_cache_set(
320             saved, credentials, provider_baton, parameters, realmstring,
321             windows_ssl_client_cert_pw_encrypter,
322             SVN_AUTH__WINCRYPT_PASSWORD_TYPE, pool);
323}
324
325static const svn_auth_provider_t windows_ssl_client_cert_pw_provider = {
326  SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
327  windows_ssl_client_cert_pw_first_creds,
328  NULL,
329  windows_ssl_client_cert_pw_save_creds
330};
331
332
333/* Public API */
334void
335svn_auth__get_windows_ssl_client_cert_pw_provider
336   (svn_auth_provider_object_t **provider,
337    apr_pool_t *pool)
338{
339  svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
340
341  po->vtable = &windows_ssl_client_cert_pw_provider;
342  *provider = po;
343}
344
345
346/*-----------------------------------------------------------------------*/
347/* Windows SSL server trust provider, validates ssl certificate using    */
348/* CryptoApi.                                                            */
349/*-----------------------------------------------------------------------*/
350
351/* Helper to create CryptoAPI CERT_CONTEXT from base64 encoded BASE64_CERT.
352 * Returns NULL on error.
353 */
354static PCCERT_CONTEXT
355certcontext_from_base64(const char *base64_cert, apr_pool_t *pool)
356{
357  PCCERT_CONTEXT cert_context = NULL;
358  int cert_len;
359  BYTE *binary_cert;
360
361  /* Use apr-util as CryptStringToBinaryA is available only on XP+. */
362  binary_cert = apr_palloc(pool,
363                           apr_base64_decode_len(base64_cert));
364  cert_len = apr_base64_decode((char*)binary_cert, base64_cert);
365
366  /* Parse the certificate into a context. */
367  cert_context = CertCreateCertificateContext
368    (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, binary_cert, cert_len);
369
370  return cert_context;
371}
372
373/* Helper for windows_ssl_server_trust_first_credentials for validating
374 * certificate using CryptoApi. Sets *OK_P to TRUE if base64 encoded ASCII_CERT
375 * certificate considered as valid.
376 */
377static svn_error_t *
378windows_validate_certificate(svn_boolean_t *ok_p,
379                             const char *ascii_cert,
380                             apr_pool_t *pool)
381{
382  PCCERT_CONTEXT cert_context = NULL;
383  CERT_CHAIN_PARA chain_para;
384  PCCERT_CHAIN_CONTEXT chain_context = NULL;
385
386  *ok_p = FALSE;
387
388  /* Parse the certificate into a context. */
389  cert_context = certcontext_from_base64(ascii_cert, pool);
390
391  if (cert_context)
392    {
393      /* Retrieve the certificate chain of the certificate
394         (a certificate without a valid root does not have a chain). */
395      memset(&chain_para, 0, sizeof(chain_para));
396      chain_para.cbSize = sizeof(chain_para);
397
398      /* Don't hit the wire for URL based objects and revocation checks, as
399         that may cause stalls, network timeouts or spurious errors in cases
400         such as with the remote OCSP and CRL endpoints being inaccessible or
401         unreliable.
402
403         For this particular case of the SVN_AUTH_SSL_UNKNOWNCA cert failure
404         override we should be okay with just the data that we have immediately
405         available on the local machine.
406       */
407      if (CertGetCertificateChain(NULL, cert_context, NULL, NULL, &chain_para,
408                                  CERT_CHAIN_CACHE_END_CERT |
409                                  CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL |
410                                  CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT |
411                                  CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY,
412                                  NULL, &chain_context))
413        {
414          CERT_CHAIN_POLICY_PARA policy_para;
415          CERT_CHAIN_POLICY_STATUS policy_status;
416
417          policy_para.cbSize = sizeof(policy_para);
418          /* We only use the local data for revocation checks, so they may
419             fail with errors like CRYPT_E_REVOCATION_OFFLINE; ignore those. */
420          policy_para.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS;
421          policy_para.pvExtraPolicyPara = NULL;
422
423          policy_status.cbSize = sizeof(policy_status);
424
425          if (CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL,
426                                               chain_context, &policy_para,
427                                               &policy_status))
428            {
429              if (policy_status.dwError == S_OK)
430                {
431                  /* Windows thinks the certificate is valid. */
432                  *ok_p = TRUE;
433                }
434            }
435
436          CertFreeCertificateChain(chain_context);
437        }
438      CertFreeCertificateContext(cert_context);
439    }
440
441  return SVN_NO_ERROR;
442}
443
444/* Retrieve ssl server CA failure overrides (if any) from CryptoApi. */
445static svn_error_t *
446windows_ssl_server_trust_first_credentials(void **credentials,
447                                           void **iter_baton,
448                                           void *provider_baton,
449                                           apr_hash_t *parameters,
450                                           const char *realmstring,
451                                           apr_pool_t *pool)
452{
453  apr_uint32_t *failure_ptr = svn_hash_gets(parameters,
454                                            SVN_AUTH_PARAM_SSL_SERVER_FAILURES);
455  apr_uint32_t failures = *failure_ptr;
456  const svn_auth_ssl_server_cert_info_t *cert_info =
457    svn_hash_gets(parameters, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO);
458
459  *credentials = NULL;
460  *iter_baton = NULL;
461
462  /* We can accept only unknown certificate authority. */
463  if (failures & SVN_AUTH_SSL_UNKNOWNCA)
464    {
465      svn_boolean_t ok;
466
467      SVN_ERR(windows_validate_certificate(&ok, cert_info->ascii_cert, pool));
468
469      /* Windows thinks that certificate is ok. */
470      if (ok)
471        {
472          /* Clear failure flag. */
473          failures &= ~SVN_AUTH_SSL_UNKNOWNCA;
474        }
475    }
476
477  /* If all failures are cleared now, we return the creds */
478  if (! failures)
479    {
480      svn_auth_cred_ssl_server_trust_t *creds =
481        apr_pcalloc(pool, sizeof(*creds));
482      creds->accepted_failures = *failure_ptr & ~failures;
483      creds->may_save = FALSE; /* No need to save it. */
484      *credentials = creds;
485    }
486
487  return SVN_NO_ERROR;
488}
489
490static const svn_auth_provider_t windows_server_trust_provider = {
491  SVN_AUTH_CRED_SSL_SERVER_TRUST,
492  windows_ssl_server_trust_first_credentials,
493  NULL,
494  NULL,
495};
496
497/* Public API */
498void
499svn_auth__get_windows_ssl_server_trust_provider
500  (svn_auth_provider_object_t **provider, apr_pool_t *pool)
501{
502  svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
503
504  po->vtable = &windows_server_trust_provider;
505  *provider = po;
506}
507
508static const svn_auth_provider_t windows_server_authority_provider = {
509    SVN_AUTH_CRED_SSL_SERVER_AUTHORITY,
510    windows_ssl_server_trust_first_credentials,
511    NULL,
512    NULL,
513};
514
515/* Public API */
516void
517svn_auth__get_windows_ssl_server_authority_provider(
518                            svn_auth_provider_object_t **provider,
519                            apr_pool_t *pool)
520{
521    svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
522
523    po->vtable = &windows_server_authority_provider;
524    *provider = po;
525}
526
527
528#else  /* !WIN32 */
529
530/* Silence OSX ranlib warnings about object files with no symbols. */
531#include <apr.h>
532extern const apr_uint32_t svn__fake__win32_crypto;
533const apr_uint32_t svn__fake__win32_crypto = 0xdeadbeef;
534
535#endif /* WIN32 */
536