3 * Windows security support.
5 * Copyright 2016 Microsoft
6 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
11 #if defined(HOST_WIN32)
14 #include "mono/metadata/mono-security-windows-internals.h"
16 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
21 #ifndef PROTECTED_DACL_SECURITY_INFORMATION
22 #define PROTECTED_DACL_SECURITY_INFORMATION 0x80000000L
25 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
27 GetSidName (gunichar2
*server
, PSID sid
, gint32
*size
)
29 gunichar2
*uniname
= NULL
;
32 SID_NAME_USE peUse
; /* out */
34 LookupAccountSid (server
, sid
, NULL
, &cchName
, NULL
,
37 if ((cchName
> 0) && (cchDomain
> 0)) {
38 gunichar2
*user
= g_malloc0 ((cchName
+ 1) * 2);
39 gunichar2
*domain
= g_malloc0 ((cchDomain
+ 1) * 2);
41 LookupAccountSid (server
, sid
, user
, &cchName
, domain
,
46 /* domain/machine name included (+ sepearator) */
47 *size
= cchName
+ cchDomain
+ 1;
48 uniname
= g_malloc0 ((*size
+ 1) * 2);
49 memcpy (uniname
, domain
, cchDomain
* 2);
50 *(uniname
+ cchDomain
) = '\\';
51 memcpy (uniname
+ cchDomain
+ 1, user
, cchName
* 2);
55 /* no domain / machine */
61 /* nothing -> return NULL */
72 mono_security_principal_windows_identity_get_current_token (void)
74 gpointer token
= NULL
;
76 /* Note: This isn't a copy of the Token - we must not close it!!!
77 * http://www.develop.com/kbrown/book/html/whatis_windowsprincipal.html
80 /* thread may be impersonating somebody */
81 if (OpenThreadToken (GetCurrentThread (), MAXIMUM_ALLOWED
, 1, &token
) == 0) {
82 /* if not take the process identity */
83 OpenProcessToken (GetCurrentProcess (), MAXIMUM_ALLOWED
, &token
);
90 ves_icall_System_Security_Principal_WindowsIdentity_GetCurrentToken (MonoError
*error
)
93 return mono_security_principal_windows_identity_get_current_token ();
97 mono_security_win_get_token_name (gpointer token
, gunichar2
** uniname
)
101 GetTokenInformation (token
, TokenUser
, NULL
, size
, (PDWORD
)&size
);
103 TOKEN_USER
*tu
= g_malloc0 (size
);
104 if (GetTokenInformation (token
, TokenUser
, tu
, size
, (PDWORD
)&size
)) {
105 *uniname
= GetSidName (NULL
, tu
->User
.Sid
, &size
);
112 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
115 ves_icall_System_Security_Principal_WindowsIdentity_GetTokenName (gpointer token
, MonoError
*error
)
117 MonoStringHandle result
;
118 gunichar2
*uniname
= NULL
;
123 size
= mono_security_win_get_token_name (token
, &uniname
);
126 result
= mono_string_new_utf16_handle (mono_domain_get (), uniname
, size
, error
);
129 result
= mono_string_new_handle (mono_domain_get (), "", error
);
138 ves_icall_System_Security_Principal_WindowsIdentity_GetUserToken (MonoStringHandle username
, MonoError
*error
)
141 gpointer token
= NULL
;
143 /* TODO: MS has something like this working in Windows 2003 (client and
144 * server) but works only for domain accounts (so it's quite limiting).
145 * http://www.develop.com/kbrown/book/html/howto_logonuser.html
147 g_warning ("Unsupported on Win32 (anyway requires W2K3 minimum)");
151 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
153 ves_icall_System_Security_Principal_WindowsIdentity_GetRoles (gpointer token
)
156 MonoArray
*array
= NULL
;
157 MonoDomain
*domain
= mono_domain_get ();
161 GetTokenInformation (token
, TokenGroups
, NULL
, size
, (PDWORD
)&size
);
163 TOKEN_GROUPS
*tg
= g_malloc0 (size
);
164 if (GetTokenInformation (token
, TokenGroups
, tg
, size
, (PDWORD
)&size
)) {
166 int num
= tg
->GroupCount
;
168 array
= mono_array_new_checked (domain
, mono_get_string_class (), num
, error
);
169 if (mono_error_set_pending_exception (error
)) {
174 for (i
=0; i
< num
; i
++) {
176 gunichar2
*uniname
= GetSidName (NULL
, tg
->Groups
[i
].Sid
, &size
);
179 MonoString
*str
= mono_string_new_utf16_checked (domain
, uniname
, size
, error
);
180 if (!is_ok (error
)) {
183 mono_error_set_pending_exception (error
);
186 mono_array_setref (array
, i
, str
);
195 /* return empty array of string, i.e. string [0] */
196 array
= mono_array_new_checked (domain
, mono_get_string_class (), 0, error
);
197 mono_error_set_pending_exception (error
);
201 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
204 ves_icall_System_Security_Principal_WindowsImpersonationContext_CloseToken (gpointer token
)
206 gboolean result
= TRUE
;
207 result
= (CloseHandle (token
) != 0);
211 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
213 ves_icall_System_Security_Principal_WindowsImpersonationContext_DuplicateToken (gpointer token
)
215 gpointer dupe
= NULL
;
217 if (DuplicateToken (token
, SecurityImpersonation
, &dupe
) == 0) {
222 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
225 ves_icall_System_Security_Principal_WindowsPrincipal_IsMemberOfGroupId (gpointer user
, gpointer group
)
227 gboolean result
= FALSE
;
229 /* The convertion from an ID to a string is done in managed code for Windows */
230 g_warning ("IsMemberOfGroupId should never be called on Win32");
235 ves_icall_System_Security_Principal_WindowsPrincipal_IsMemberOfGroupName (gpointer user
, MonoString
*group
)
237 gboolean result
= FALSE
;
239 /* Windows version use a cache built using WindowsIdentity._GetRoles */
240 g_warning ("IsMemberOfGroupName should never be called on Win32");
244 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
246 GetAdministratorsSid (void)
248 SID_IDENTIFIER_AUTHORITY admins
= { SECURITY_NT_AUTHORITY
};
250 if (!AllocateAndInitializeSid (&admins
, 2, SECURITY_BUILTIN_DOMAIN_RID
,
251 DOMAIN_ALIAS_RID_ADMINS
, 0, 0, 0, 0, 0, 0, &pSid
))
253 /* Note: this SID must be freed with FreeSid () */
258 GetEveryoneSid (void)
260 SID_IDENTIFIER_AUTHORITY everyone
= { SECURITY_WORLD_SID_AUTHORITY
};
262 if (!AllocateAndInitializeSid (&everyone
, 1, SECURITY_WORLD_RID
, 0, 0, 0, 0, 0, 0, 0, &pSid
))
264 /* Note: this SID must be freed with FreeSid () */
269 GetCurrentUserSid (void)
273 gpointer token
= mono_security_principal_windows_identity_get_current_token ();
275 GetTokenInformation (token
, TokenUser
, NULL
, size
, (PDWORD
)&size
);
277 TOKEN_USER
*tu
= g_malloc0 (size
);
278 if (GetTokenInformation (token
, TokenUser
, tu
, size
, (PDWORD
)&size
)) {
279 DWORD length
= GetLengthSid (tu
->User
.Sid
);
280 sid
= (PSID
) g_malloc0 (length
);
281 if (!CopySid (length
, sid
, tu
->User
.Sid
)) {
288 /* Note: this SID must be freed with g_free () */
293 GetRightsFromSid (PSID sid
, PACL acl
)
295 ACCESS_MASK rights
= 0;
298 BuildTrusteeWithSidW (&trustee
, sid
);
299 if (GetEffectiveRightsFromAcl (acl
, &trustee
, &rights
) != ERROR_SUCCESS
)
306 mono_security_win_is_machine_protected (gunichar2
*path
)
308 gboolean success
= FALSE
;
310 PSECURITY_DESCRIPTOR pSD
= NULL
;
311 PSID pEveryoneSid
= NULL
;
313 DWORD dwRes
= GetNamedSecurityInfoW (path
, SE_FILE_OBJECT
, DACL_SECURITY_INFORMATION
, NULL
, NULL
, &pDACL
, NULL
, &pSD
);
314 if (dwRes
!= ERROR_SUCCESS
)
317 /* We check that Everyone is still limited to READ-ONLY -
318 but not if new entries have been added by an Administrator */
320 pEveryoneSid
= GetEveryoneSid ();
322 ACCESS_MASK rights
= GetRightsFromSid (pEveryoneSid
, pDACL
);
323 /* http://msdn.microsoft.com/library/en-us/security/security/generic_access_rights.asp?frame=true */
324 success
= (rights
== (READ_CONTROL
| SYNCHRONIZE
| FILE_READ_DATA
| FILE_READ_EA
| FILE_READ_ATTRIBUTES
));
325 FreeSid (pEveryoneSid
);
327 /* Note: we don't need to check our own access -
328 we'll know soon enough when reading the file */
337 mono_security_win_is_user_protected (gunichar2
*path
)
339 gboolean success
= FALSE
;
341 PSID pEveryoneSid
= NULL
;
342 PSECURITY_DESCRIPTOR pSecurityDescriptor
= NULL
;
344 DWORD dwRes
= GetNamedSecurityInfoW (path
, SE_FILE_OBJECT
,
345 DACL_SECURITY_INFORMATION
, NULL
, NULL
, &pDACL
, NULL
, &pSecurityDescriptor
);
346 if (dwRes
!= ERROR_SUCCESS
)
349 /* We check that our original entries in the ACL are in place -
350 but not if new entries have been added by the user */
352 /* Everyone should be denied */
353 pEveryoneSid
= GetEveryoneSid ();
355 ACCESS_MASK rights
= GetRightsFromSid (pEveryoneSid
, pDACL
);
356 success
= (rights
== 0);
357 FreeSid (pEveryoneSid
);
359 /* Note: we don't need to check our own access -
360 we'll know soon enough when reading the file */
362 if (pSecurityDescriptor
)
363 LocalFree (pSecurityDescriptor
);
369 mono_security_win_protect_machine (gunichar2
*path
)
371 PSID pEveryoneSid
= GetEveryoneSid ();
372 PSID pAdminsSid
= GetAdministratorsSid ();
375 if (pEveryoneSid
&& pAdminsSid
) {
377 EXPLICIT_ACCESS ea
[2];
378 ZeroMemory (&ea
, 2 * sizeof (EXPLICIT_ACCESS
));
380 /* grant all access to the BUILTIN\Administrators group */
381 BuildTrusteeWithSidW (&ea
[0].Trustee
, pAdminsSid
);
382 ea
[0].grfAccessPermissions
= GENERIC_ALL
;
383 ea
[0].grfAccessMode
= SET_ACCESS
;
384 ea
[0].grfInheritance
= SUB_CONTAINERS_AND_OBJECTS_INHERIT
;
385 ea
[0].Trustee
.TrusteeForm
= TRUSTEE_IS_SID
;
386 ea
[0].Trustee
.TrusteeType
= TRUSTEE_IS_WELL_KNOWN_GROUP
;
388 /* read-only access everyone */
389 BuildTrusteeWithSidW (&ea
[1].Trustee
, pEveryoneSid
);
390 ea
[1].grfAccessPermissions
= GENERIC_READ
;
391 ea
[1].grfAccessMode
= SET_ACCESS
;
392 ea
[1].grfInheritance
= SUB_CONTAINERS_AND_OBJECTS_INHERIT
;
393 ea
[1].Trustee
.TrusteeForm
= TRUSTEE_IS_SID
;
394 ea
[1].Trustee
.TrusteeType
= TRUSTEE_IS_WELL_KNOWN_GROUP
;
396 retval
= SetEntriesInAcl (2, ea
, NULL
, &pDACL
);
397 if (retval
== ERROR_SUCCESS
) {
398 /* with PROTECTED_DACL_SECURITY_INFORMATION we */
399 /* remove any existing ACL (like inherited ones) */
400 retval
= SetNamedSecurityInfo (path
, SE_FILE_OBJECT
,
401 DACL_SECURITY_INFORMATION
| PROTECTED_DACL_SECURITY_INFORMATION
,
402 NULL
, NULL
, pDACL
, NULL
);
409 FreeSid (pEveryoneSid
);
411 FreeSid (pAdminsSid
);
412 return (retval
== ERROR_SUCCESS
);
416 mono_security_win_protect_user (gunichar2
*path
)
420 PSID pCurrentSid
= GetCurrentUserSid ();
424 ZeroMemory (&ea
, sizeof (EXPLICIT_ACCESS
));
426 /* grant exclusive access to the current user */
427 BuildTrusteeWithSidW (&ea
.Trustee
, pCurrentSid
);
428 ea
.grfAccessPermissions
= GENERIC_ALL
;
429 ea
.grfAccessMode
= SET_ACCESS
;
430 ea
.grfInheritance
= SUB_CONTAINERS_AND_OBJECTS_INHERIT
;
431 ea
.Trustee
.TrusteeForm
= TRUSTEE_IS_SID
;
432 ea
.Trustee
.TrusteeType
= TRUSTEE_IS_USER
;
434 retval
= SetEntriesInAcl (1, &ea
, NULL
, &pDACL
);
435 if (retval
== ERROR_SUCCESS
) {
436 /* with PROTECTED_DACL_SECURITY_INFORMATION we
437 remove any existing ACL (like inherited ones) */
438 retval
= SetNamedSecurityInfo (path
, SE_FILE_OBJECT
,
439 DACL_SECURITY_INFORMATION
| PROTECTED_DACL_SECURITY_INFORMATION
,
440 NULL
, NULL
, pDACL
, NULL
);
445 g_free (pCurrentSid
); /* g_malloc0 */
448 return (retval
== ERROR_SUCCESS
);
450 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
453 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_CanSecure (MonoString
*root
)
457 /* ACL are nice... unless you have FAT or other uncivilized filesystem */
458 if (!GetVolumeInformation (mono_string_chars (root
), NULL
, 0, NULL
, NULL
, (LPDWORD
)&flags
, NULL
, 0))
460 return ((flags
& FS_PERSISTENT_ACLS
) == FS_PERSISTENT_ACLS
);
464 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_IsMachineProtected (MonoString
*path
)
466 gboolean ret
= FALSE
;
468 /* no one, but the owner, should have write access to the directory */
469 ret
= mono_security_win_is_machine_protected (mono_string_chars (path
));
470 return (MonoBoolean
)ret
;
474 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_IsUserProtected (MonoString
*path
)
476 gboolean ret
= FALSE
;
478 /* no one, but the user, should have access to the directory */
479 ret
= mono_security_win_is_user_protected (mono_string_chars (path
));
480 return (MonoBoolean
)ret
;
484 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_ProtectMachine (MonoString
*path
)
486 gboolean ret
= FALSE
;
488 /* read/write to owner, read to everyone else */
489 ret
= mono_security_win_protect_machine (mono_string_chars (path
));
490 return (MonoBoolean
)ret
;
494 ves_icall_Mono_Security_Cryptography_KeyPairPersistence_ProtectUser (MonoString
*path
)
496 gboolean ret
= FALSE
;
498 /* read/write to user, no access to everyone else */
499 ret
= mono_security_win_protect_user (mono_string_chars (path
));
500 return (MonoBoolean
)ret
;
502 #endif /* HOST_WIN32 */