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 */
25 typedef 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. */
54 static 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. */
59 const svn_string_t *
encrypt_data(const svn_string_t * orig,apr_pool_t * pool)60 encrypt_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. */
81 const svn_string_t *
decrypt_data(const svn_string_t * crypted,apr_pool_t * pool)82 decrypt_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. */
111 static svn_error_t *
windows_password_encrypter(svn_boolean_t * done,apr_hash_t * creds,const char * realmstring,const char * username,const char * in,apr_hash_t * parameters,svn_boolean_t non_interactive,apr_pool_t * pool)112 windows_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. */
138 static svn_error_t *
windows_password_decrypter(svn_boolean_t * done,const char ** out,apr_hash_t * creds,const char * realmstring,const char * username,apr_hash_t * parameters,svn_boolean_t non_interactive,apr_pool_t * pool)139 windows_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. */
171 static svn_error_t *
windows_simple_first_creds(void ** credentials,void ** iter_baton,void * provider_baton,apr_hash_t * parameters,const char * realmstring,apr_pool_t * pool)172 windows_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. */
190 static svn_error_t *
windows_simple_save_creds(svn_boolean_t * saved,void * credentials,void * provider_baton,apr_hash_t * parameters,const char * realmstring,apr_pool_t * pool)191 windows_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 
207 static 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 */
216 void
svn_auth__get_windows_simple_provider(svn_auth_provider_object_t ** provider,apr_pool_t * pool)217 svn_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. */
234 static svn_error_t *
windows_ssl_client_cert_pw_encrypter(svn_boolean_t * done,apr_hash_t * creds,const char * realmstring,const char * username,const char * in,apr_hash_t * parameters,svn_boolean_t non_interactive,apr_pool_t * pool)235 windows_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. */
262 static svn_error_t *
windows_ssl_client_cert_pw_decrypter(svn_boolean_t * done,const char ** out,apr_hash_t * creds,const char * realmstring,const char * username,apr_hash_t * parameters,svn_boolean_t non_interactive,apr_pool_t * pool)263 windows_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. */
296 static svn_error_t *
windows_ssl_client_cert_pw_first_creds(void ** credentials,void ** iter_baton,void * provider_baton,apr_hash_t * parameters,const char * realmstring,apr_pool_t * pool)297 windows_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. */
311 static svn_error_t *
windows_ssl_client_cert_pw_save_creds(svn_boolean_t * saved,void * credentials,void * provider_baton,apr_hash_t * parameters,const char * realmstring,apr_pool_t * pool)312 windows_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 
325 static 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 */
334 void
svn_auth__get_windows_ssl_client_cert_pw_provider(svn_auth_provider_object_t ** provider,apr_pool_t * pool)335 svn_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  */
354 static PCCERT_CONTEXT
certcontext_from_base64(const char * base64_cert,apr_pool_t * pool)355 certcontext_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  */
377 static svn_error_t *
windows_validate_certificate(svn_boolean_t * ok_p,const char * ascii_cert,apr_pool_t * pool)378 windows_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. */
445 static svn_error_t *
windows_ssl_server_trust_first_credentials(void ** credentials,void ** iter_baton,void * provider_baton,apr_hash_t * parameters,const char * realmstring,apr_pool_t * pool)446 windows_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 
490 static 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 */
498 void
svn_auth__get_windows_ssl_server_trust_provider(svn_auth_provider_object_t ** provider,apr_pool_t * pool)499 svn_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 
508 static 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 */
516 void
svn_auth__get_windows_ssl_server_authority_provider(svn_auth_provider_object_t ** provider,apr_pool_t * pool)517 svn_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>
532 extern const apr_uint32_t svn__fake__win32_crypto;
533 const apr_uint32_t svn__fake__win32_crypto = 0xdeadbeef;
534 
535 #endif /* WIN32 */
536