From b8ebe98956b60f1d1a09350a97e9e6b1b079c274 Mon Sep 17 00:00:00 2001 From: Aaron Jensen Date: Mon, 11 Mar 2024 10:55:00 -0700 Subject: [PATCH] Removing private key functionality as it has been migrated to Carbon.Cryptography. --- CHANGELOG.md | 38 +- ...ConvertTo-CProviderAccessControlRights.ps1 | 94 ----- .../Functions/ConvertTo-Flags.ps1 | 85 ---- Carbon.Permissions/Functions/Get-CAcl.ps1 | 2 +- .../Functions/Get-CPermission.ps1 | 70 +--- .../Functions/Grant-CPermission.ps1 | 370 ++++++------------ Carbon.Permissions/Functions/Resolve-Arg.ps1 | 192 +++++++++ .../Functions/Resolve-CPrivateKeyPath.ps1 | 205 ---------- .../Functions/Revoke-CPermission.ps1 | 117 ++---- .../Functions/Set-CCryptoKeySecurity.ps1 | 46 --- .../Functions/Test-CCryptoKeyAvailable.ps1 | 4 - .../Functions/Test-CPermission.ps1 | 143 +++---- .../Carbon.PermissionsTestHelper.psm1 | 12 - ...tTo-CProviderAccessControlRights.Tests.ps1 | 42 -- Tests/Get-CPermission.Tests.ps1 | 71 ---- Tests/Grant-CPermission.Tests.ps1 | 158 +------- Tests/Revoke-CPermission.Tests.ps1 | 79 +--- Tests/Test-CPermission.Tests.ps1 | 54 +-- prism.json | 4 - prism.lock.json | 5 - 20 files changed, 441 insertions(+), 1350 deletions(-) delete mode 100644 Carbon.Permissions/Functions/ConvertTo-CProviderAccessControlRights.ps1 delete mode 100644 Carbon.Permissions/Functions/ConvertTo-Flags.ps1 create mode 100644 Carbon.Permissions/Functions/Resolve-Arg.ps1 delete mode 100644 Carbon.Permissions/Functions/Resolve-CPrivateKeyPath.ps1 delete mode 100644 Carbon.Permissions/Functions/Set-CCryptoKeySecurity.ps1 delete mode 100644 Carbon.Permissions/Functions/Test-CCryptoKeyAvailable.ps1 delete mode 100644 Tests/ConvertTo-CProviderAccessControlRights.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c42945..36d05e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,37 @@ ### Upgrade Instructions -Replaces usages of the `Grant-CPermission` and `Test-CPermission` functions' `ApplyTo` parameter with new parameter +This is not the upgrade path you want, if switching from Carbon. The `Get-CPermission`, `Grant-CPermission`, +`Revoke-CPermission`, and `Test-CPermission` functions were migrated to the following modules with the following +function names. + +`Carbon.FileSystem`: + +* `Get-CNtfsPermission` +* `Grant-CNtfsPermission` +* `Revoke-CNtfsPermission` +* `Test-CNtfsPermission` + +`Carbon.Registry`: + +* `Get-CRegistryPermission` +* `Grant-CRegistryPermission` +* `Revoke-CRegistryPermission` +* `Test-CRegistryPermission`: + +`Carbon.Cryptography`: + +* `Get-CPrivateKey` +* `Get-CPrivateKeyPermission` +* `Grant-CPrivateKeyPermission` +* `Resolve-CPrivateKeyPath` +* `Revoke-CPrivateKeyPermission` +* `Test-CPrivateKeyPath` + +You *must* switch to `Carbon.Cryptography` if managing permissions on private keys/key containers. `Carbon.Permissions` +only manages permissions on files, directories, and registry keys. + +Replace usages of the `Grant-CPermission` and `Test-CPermission` functions' `ApplyTo` parameter with new parameter values and a new `OnlyApplyToChildren` switch: | Old Parameters | New Parameters @@ -44,7 +74,9 @@ Supports getting only specific sections/parts of the security descriptor, too. ### Removed -* The `ApplyTo` function on `Grant-CPermission` and `Test-CPermission`. Use the new `InheritanceFlag` and -`PropagationFlag` parameters to set a permission's inheritance and propagation flags. * Alias `Get-Permissions`. Use `Get-CPermission` instead. * Alias `Grant-Permissions`. Use `Grant-CPermission` instead. +* Private key/key container support from `Get-CPermission`, `Grant-CPermission`, `Revoke-CPermission`, and +`Test-CPermission`. Switch to the `Carbon.Cryptography` module's `Get-CPrivateKey`, `Get-CPrivateKeyPermission`, +`Grant-CPrivateKeyPermission`, `Resolve-CPrivateKeyPath`, `Revoke-CPrivateKeyPermission`, and `Test-CPrivateKeyPath` +instead. \ No newline at end of file diff --git a/Carbon.Permissions/Functions/ConvertTo-CProviderAccessControlRights.ps1 b/Carbon.Permissions/Functions/ConvertTo-CProviderAccessControlRights.ps1 deleted file mode 100644 index 2c00d55..0000000 --- a/Carbon.Permissions/Functions/ConvertTo-CProviderAccessControlRights.ps1 +++ /dev/null @@ -1,94 +0,0 @@ - -function ConvertTo-CProviderAccessControlRights -{ - <# - .SYNOPSIS - Converts strings into the appropriate access control rights for a PowerShell provider (e.g. FileSystemRights or - RegistryRights). - - .DESCRIPTION - This is an internal Carbon function, so you're not getting anything more than the synopsis. - - .EXAMPLE - ConvertTo-CProviderAccessControlRights -ProviderName 'FileSystem' -InputObject 'Read','Write' - - Demonstrates how to convert `Read` and `Write` into a `System.Security.AccessControl.FileSystemRights` value. - #> - [CmdletBinding()] - param( - # The provider name. - [Parameter(Mandatory)] - [ValidateSet('FileSystem', 'Registry', 'CryptoKey')] - [String] $ProviderName, - - # The values to convert. - [Parameter(Mandatory, ValueFromPipeline)] - [String[]] $InputObject - ) - - begin - { - Set-StrictMode -Version 'Latest' - Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState - - $toFS = $ProviderName -eq 'FileSystem' - $rightTypeName = 'Security.AccessControl.{0}Rights' -f $ProviderName - - # CryptoKey does not exist in .NET standard/core so we will have to use FileSystem instead - if ($ProviderName -eq 'CryptoKey' -and -not (Test-CCryptoKeyAvailable)) - { - $toFS = $true - $rightTypeName = 'Security.AccessControl.FileSystemRights' - } - - $rights = 0 -as $rightTypeName - - $foundInvalidRight = $false - - $genericToFSMap = @{ - GenericAll = 'FullControl'; - GenericExecute = 'ExecuteFile'; - GenericWrite = 'Write'; - GenericRead = 'Read'; - } - Write-Debug "[ConvertTo-CProviderAccessControlRights]" - } - - process - { - Write-Debug " ${InputObject}" - foreach ($value in $InputObject) - { - if ($toFS -and $genericToFSMap.ContainsKey($value)) - { - $value = $genericToFSMap[$value] - } - - $right = $value -as $rightTypeName - if (-not $right) - { - $allowedValues = [Enum]::GetNames($rightTypeName) - Write-Error ("System.Security.AccessControl.{0}Rights value '{1}' not found. Must be one of: {2}." -f $providerName,$_,($allowedValues -join ' ')) - $foundInvalidRight = $true - return - } - Write-Debug " ${value} → ${right}/0x$($right.ToString('x'))" - $rights = $rights -bor $right - } - } - - end - { - if( $foundInvalidRight ) - { - Write-Debug " null" - return $null - } - else - { - Write-Debug " ${rights}/0x$($rights.ToString('x'))" - $rights - } - Write-Debug "[ConvertTo-CProviderAccessControlRights]" - } -} diff --git a/Carbon.Permissions/Functions/ConvertTo-Flags.ps1 b/Carbon.Permissions/Functions/ConvertTo-Flags.ps1 deleted file mode 100644 index 2131c52..0000000 --- a/Carbon.Permissions/Functions/ConvertTo-Flags.ps1 +++ /dev/null @@ -1,85 +0,0 @@ - -function ConvertTo-Flags -{ - [CmdletBinding()] - param( - [Parameter(Mandatory)] - [ValidateSet('ContainerOnly', 'ContainerSubcontainersAndLeaves', 'ContainerAndSubcontainers', - 'ContainerAndLeaves', 'SubcontainersAndLeavesOnly', 'SubcontainersOnly', 'LeavesOnly')] - [String] $ApplyTo, - - [switch] $OnlyApplyToChildren - ) - - Set-StrictMode -Version 'Latest' - Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState - - # ApplyTo OnlyApplyToChildren InheritanceFlags PropagationFlags - # ------- ------------------- ---------------- ---------------- - # ContainerOnly true None None - # ContainerSubcontainersAndLeaves true ContainerInherit, ObjectInherit NoPropagateInherit - # ContainerAndSubcontainers true ContainerInherit NoPropagateInherit - # ContainerAndLeaves true ObjectInherit NoPropagateInherit - # SubcontainersAndLeavesOnly true ContainerInherit, ObjectInherit NoPropagateInherit, InheritOnly - # SubcontainersOnly true ContainerInherit NoPropagateInherit, InheritOnly - # LeavesOnly true ObjectInherit NoPropagateInherit, InheritOnly - # ContainerOnly false None None - # ContainerSubcontainersAndLeaves false ContainerInherit, ObjectInherit None - # ContainerAndSubcontainers false ContainerInherit None - # ContainerAndLeaves false ObjectInherit None - # SubcontainersAndLeavesOnly false ContainerInherit, ObjectInherit InheritOnly - # SubcontainersOnly false ContainerInherit InheritOnly - # LeavesOnly false ObjectInherit InheritOnly - - $inheritanceFlags = [InheritanceFlags]::None - $propagationFlags = [PropagationFlags]::None - - switch ($ApplyTo) - { - 'ContainerOnly' - { - $inheritanceFlags = [InheritanceFlags]::None - $propagationFlags = [PropagationFlags]::None - } - 'ContainerSubcontainersAndLeaves' - { - $inheritanceFlags = [InheritanceFlags]::ContainerInherit -bor [InheritanceFlags]::ObjectInherit - $propagationFlags = [PropagationFlags]::None - } - 'ContainerAndSubcontainers' - { - $inheritanceFlags = [InheritanceFlags]::ContainerInherit - $propagationFlags = [PropagationFlags]::None - } - 'ContainerAndLeaves' - { - $inheritanceFlags = [InheritanceFlags]::ObjectInherit - $propagationFlags = [PropagationFlags]::None - } - 'SubcontainersAndLeavesOnly' - { - $inheritanceFlags = [InheritanceFlags]::ContainerInherit -bor [InheritanceFlags]::ObjectInherit - $propagationFlags = [PropagationFlags]::InheritOnly - } - 'SubcontainersOnly' - { - $inheritanceFlags = [InheritanceFlags]::ContainerInherit - $propagationFlags = [PropagationFlags]::InheritOnly - } - 'LeavesOnly' - { - $inheritanceFlags = [InheritanceFlags]::ObjectInherit - $propagationFlags = [PropagationFlags]::InheritOnly - } - } - - if ($OnlyApplyToChildren -and $ApplyTo -ne 'ContainerOnly') - { - $propagationFlags = $propagationFlags -bor [PropagationFlags]::NoPropagateInherit - } - - return [pscustomobject]@{ - InheritanceFlags = $inheritanceFlags; - PropagationFlags = $propagationFlags; - } -} \ No newline at end of file diff --git a/Carbon.Permissions/Functions/Get-CAcl.ps1 b/Carbon.Permissions/Functions/Get-CAcl.ps1 index dd1fb1c..9775e7e 100644 --- a/Carbon.Permissions/Functions/Get-CAcl.ps1 +++ b/Carbon.Permissions/Functions/Get-CAcl.ps1 @@ -55,7 +55,7 @@ function Get-CAcl if ($InputObject -isnot [FileSystemInfo]) { $msg = "Failed to get ACL for ""${InputObject}"" because it doesn't have a ""GetAccessControl"" member " + - "and is not a FileInfo or DirectoryInfo object." + "and is a [$($InputObject.GetType().FullName)] object and not a FileInfo or DirectoryInfo object." Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } diff --git a/Carbon.Permissions/Functions/Get-CPermission.ps1 b/Carbon.Permissions/Functions/Get-CPermission.ps1 index a148891..42ffb18 100644 --- a/Carbon.Permissions/Functions/Get-CPermission.ps1 +++ b/Carbon.Permissions/Functions/Get-CPermission.ps1 @@ -3,12 +3,11 @@ function Get-CPermission { <# .SYNOPSIS - Gets the permissions (access control rules) for a file, directory, registry key, or certificate private key/key - container. + Gets the permissions (access control rules) for a file, directory, or registry key. .DESCRIPTION - The `Get-CPermission` function gets the permissions, as access control rule objects, for a file, directory, registry - key, or a certificate private key/key container. Using this function and module are not recommended. Instead, + The `Get-CPermission` function gets the permissions, as access control rule objects, for a file, directory, or + registry key. Using this function and module are not recommended. Instead, * for file directory permissions, use `Get-CNtfsPermission` in the `Carbon.FileSystem` module. * for registry permissions, use `Get-CRegistryPermission` in the `Carbon.Registry` module. @@ -18,10 +17,7 @@ function Get-CPermission Pass the path to the `Path` parameter. By default, all non-inherited permissions on that item are returned. To return inherited permissions, use the `Inherited` switch. - To return the permissions for a specific identity, pass the identity's name to the `Identity` parameter. - - Certificate permissions are only returned if a certificate has a private key/key container. If a certificate doesn't - have a private key, `$null` is returned. + To return the permissions for a specific user or group, pass the account's name to the `Identity` parameter. .OUTPUTS System.Security.AccessControl.AccessRule. @@ -55,23 +51,16 @@ function Get-CPermission Returns `System.Security.AccessControl.FileSystemAccessRule` objects for all the `Administrators'` rules on `C:\windows`. - - .EXAMPLE - Get-CPermission -Path 'Cert:\LocalMachine\1234567890ABCDEF1234567890ABCDEF12345678' - - Returns `System.Security.AccessControl.CryptoKeyAccesRule` objects for certificate's - `Cert:\LocalMachine\1234567890ABCDEF1234567890ABCDEF12345678` private key/key container. If it doesn't have a - private key, `$null` is returned. #> [CmdletBinding()] [OutputType([System.Security.AccessControl.AccessRule])] param( - # The path whose permissions (i.e. access control rules) to return. File system, registry, or certificate paths - # supported. Wildcards supported. For certificate private keys, pass a certificate provider path, e.g. `cert:`. + # The path whose permissions (i.e. access control rules) to return. File system or registry paths supported. + # Wildcards supported. [Parameter(Mandatory)] [String] $Path, - # The identity whose permissiosn (i.e. access control rules) to return. + # The user/group name whose permissiosn (i.e. access control rules) to return. [String] $Identity, # Return inherited permissions in addition to explicit permissions. @@ -81,57 +70,26 @@ function Get-CPermission Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState - $account = $null - if( $Identity ) + $rArgs = Resolve-Arg -Path $Path -Identity $Identity -Action 'get' + if (-not $rArgs) { - $account = Test-CIdentity -Name $Identity -PassThru - if( $account ) - { - $Identity = $account.FullName - } - } - - if( -not (Test-Path -Path $Path) ) - { - Write-Error ('Path ''{0}'' not found.' -f $Path) return } - & { - foreach ($item in (Get-Item -Path $Path -Force)) - { - if( $item.PSProvider.Name -ne 'Certificate' ) - { - $item | Get-CAcl -IncludeSection ([AccessControlSections]::Access) | Write-Output - continue - } - - if (-not $item.HasPrivateKey) - { - continue - } - - if ($item.PrivateKey -and ($item.PrivateKey | Get-Member 'CspKeyContainerInfo')) - { - $item.PrivateKey.CspKeyContainerInfo.CryptoKeySecurity | Write-Output - continue - } - - $item | Resolve-CPrivateKeyPath | Get-Acl | Write-Output - } - } | + Get-Item -Path $Path -Force | + Get-CAcl -IncludeSection ([AccessControlSections]::Access) | Select-Object -ExpandProperty 'Access' | Where-Object { - if( $Inherited ) + if ($Inherited) { return $true } return (-not $_.IsInherited) } | Where-Object { - if( $Identity ) + if ($Identity) { - return ($_.IdentityReference.Value -eq $Identity) + return ($_.IdentityReference.Value -eq $rArgs.AccountName) } return $true diff --git a/Carbon.Permissions/Functions/Grant-CPermission.ps1 b/Carbon.Permissions/Functions/Grant-CPermission.ps1 index 72c15a3..8b3bdfc 100644 --- a/Carbon.Permissions/Functions/Grant-CPermission.ps1 +++ b/Carbon.Permissions/Functions/Grant-CPermission.ps1 @@ -2,41 +2,39 @@ { <# .SYNOPSIS - Grants permissions on a file, directory, registry key, or certificate private key/key container. + Grants permissions on a file, directory, or registry key. .DESCRIPTION - The `Grant-CPermission` function grants permissions to files, directories, registry keys, and certificate private - key/key containers. Using this function and module are not recommended. Instead, + The `Grant-CPermission` function grants permissions to files, directories, or registry keys. Using this function and + module are not recommended. Instead, - * for file directory permissions, use `Grant-CNtfsPermission` in the `Carbon.FileSystem` module. + * for file/directory permissions, use `Grant-CNtfsPermission` in the `Carbon.FileSystem` module. * for registry permissions, use `Grant-CRegistryPermission` in the `Carbon.Registry` module. * for private key and/or key container permissions, use `Grant-CPrivateKeyPermission` in the `Carbon.Cryptography` module. - Pass the item's path to the `Path` parameter, the name of the identity receiving the permission to the `Identity` + Pass the item's path to the `Path` parameter, the name of the user/group receiving the permission to the `Identity` parameter, and the permission to grant to the `Permission` parameter. If the identity doesn't have the permission, the item's ACL is updated to include the new permission. If the identity has permission, but it doesn't match the - permission being set, the user's current permissions are changed to match. If the user already has the given + permission being set, the identity's current permissions are changed to match. If the identity already has the given permission, nothing happens. Inherited permissions are ignored. To always grant permissions, use the `Force` (switch). The `Permissions` attribute should be a list of - [FileSystemRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx), - [RegistryRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx), or - [CryptoKeyRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.cryptokeyrights.aspx), for - files/directories, registry keys, and certificate private keys, respectively. These commands will show you the - values for the appropriate permissions for your object: + [FileSystemRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx) or + [RegistryRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx), for + files/directories or registry keys, respectively. These commands will show you the values for the appropriate + permissions for your object: [Enum]::GetValues([Security.AccessControl.FileSystemRights]) [Enum]::GetValues([Security.AccessControl.RegistryRights]) - [Enum]::GetValues([Security.AccessControl.CryptoKeyRights]) To get back the access rule, use the `PassThru` switch. By default, an `Allow` access rule is created and granted. To create a `Deny` access rule, pass `Deny` to the `Type` parameter. - To append/add permissions instead or replacing existing permissions on use the `Append` switch. + To append/add permissions instead of replacing existing permissions, use the `Append` switch. To control how the permission is applied and inherited, use the `ApplyTo` and `OnlyApplyToChildren` parameters. These behave like the "Applies to" and "Only apply these permissions to objects and/or containers within this @@ -60,16 +58,10 @@ | SubcontainersOnly | true | ContainerInherit | NoPropagateInherit, InheritOnly | LeavesOnly | true | ObjectInherit | NoPropagateInherit, InheritOnly - To remove all other non-inherited permissions from the item, use the `Clear` switch. When using the `-Clear` switch - and setting permissions on a private key in Windows PowerShell and the key is not a crypograhic next generation key, - the local `Administrators` account will always remain. In testing on Windows 2012 R2, we noticed that when - `Administrators` access was removed, you couldn't read the key anymore. - .OUTPUTS System.Security.AccessControl.AccessRule. When setting permissions on a file or directory, a `System.Security.AccessControl.FileSystemAccessRule` is returned. When setting permissions on a registry key, a - `System.Security.AccessControl.RegistryAccessRule` returned. When setting permissions on a private key, a - `System.Security.AccessControl.CryptoKeyAccessRule` object is returned. + `System.Security.AccessControl.RegistryAccessRule` returned. .LINK Get-CPermission @@ -86,9 +78,6 @@ .LINK http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx - .LINK - http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.cryptokeyrights.aspx - .LINK http://msdn.microsoft.com/en-us/magazine/cc163885.aspx#S3 @@ -110,12 +99,6 @@ Grants the Enterprise's engineering group full control on the engine room. Any non-inherited, existing access rules are removed from `C:\EngineRoom`. - .EXAMPLE - Grant-CPermission -Identity ENTERPRISE\Engineers -Permission FullControl -Path 'cert:\LocalMachine\My\1234567890ABCDEF1234567890ABCDEF12345678' - - Grants the Enterprise's engineering group full control on the `1234567890ABCDEF1234567890ABCDEF12345678` - certificate's private key/key container. - .EXAMPLE Grant-CPermission -Identity BORG\Locutus -Permission FullControl -Path 'C:\EngineRoom' -Type Deny @@ -133,9 +116,8 @@ [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='ApplyToContainersSubcontainersAndLeaves')] [OutputType([Security.AccessControl.AccessRule])] param( - # The path on which the permissions should be granted. Can be a file system, registry, or certificate path.If - # the path is relative, it uses the current location to determine the full path. For certificate private keys, - # pass a certificate provider path, e.g. `cert:`. + # The path on which the permissions should be granted. Can be a file system or registry path. If the path is + # relative, it uses the current location to determine the full path. [Parameter(Mandatory)] [String] $Path, @@ -166,9 +148,6 @@ [AccessControlType] $Type = [AccessControlType]::Allow, # Removes all non-inherited permissions on the item. - # - # If this is set and `Path` is to a non-cryptographic next generation key, and runnning under Windows - # PowerShell, Administrator permissions will never be removed. [switch] $Clear, # Returns an object representing the permission created or set on the `Path`. The returned object will have a @@ -179,8 +158,7 @@ [switch] $Force, # When granting permissions on files, directories, or registry items, add the permissions as a new access rule - # instead of replacing any existing access rules. This switch is ignored when setting permissions on private - # keys. + # instead of replacing any existing access rules. [switch] $Append, # ***Internal.*** Do not use. @@ -190,261 +168,139 @@ Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState - $Path = Resolve-Path -Path $Path - if( -not $Path ) - { - return - } - - $providerName = Get-CPathProvider -Path $Path | Select-Object -ExpandProperty 'Name' - if( $providerName -eq 'Certificate' ) - { - $providerName = 'CryptoKey' - } - - if( $providerName -ne 'Registry' -and $providerName -ne 'FileSystem' -and $providerName -ne 'CryptoKey' ) - { - Write-Error "Unsupported path: '$Path' belongs to the '$providerName' provider. Only file system, registry, and certificate paths are supported." - return - } - - $rights = $Permission | ConvertTo-CProviderAccessControlRights -ProviderName $providerName - if (-not $rights) + if (-not $ApplyTo) { - Write-Error ('Unable to grant {0} {1} permissions on {2}: received an unknown permission.' -f $Identity,($Permission -join ','),$Path) - return + $ApplyTo = 'ContainerSubcontainersAndLeaves' } - if( -not (Test-CIdentity -Name $Identity) ) + $rArgs = Resolve-Arg -Path $Path ` + -Identity $Identity ` + -Permission $Permission ` + -ApplyTo $ApplyTo ` + -OnlyApplyToChildren:$OnlyApplyToChildren ` + -Action 'grant' + if (-not $rArgs) { - Write-Error ('Identity ''{0}'' not found.' -f $Identity) return } - $Identity = Resolve-CIdentityName -Name $Identity + $providerName = $rArgs.ProviderName + $rights = $rArgs.Rights + $accountName = $rArgs.AccountName + $inheritanceFlags = $rArgs.InheritanceFlags + $propagationFlags = $rArgs.PropagationFlags - if ($providerName -eq 'CryptoKey') + foreach ($currentPath in $rArgs.Paths) { - foreach ($certificate in (Get-Item -Path $Path)) + # We don't use Get-Acl because it returns the whole security descriptor, which includes owner information. When + # passed to Set-Acl, this causes intermittent errors. So, we just grab the ACL portion of the security + # descriptor. See + # http://www.bilalaslam.com/2010/12/14/powershell-workaround-for-the-security-identifier-is-not-allowed-to-be-the-owner-of-this-object-with-set-acl/ + $currentAcl = Get-Item -LiteralPath $currentPath -Force | Get-CAcl -IncludeSection ([AccessControlSections]::Access) + + $testPermsFlagsArgs = @{ } + if (Test-Path -LiteralPath $currentPath -PathType Container) + { + $testPermsFlagsArgs['ApplyTo'] = $ApplyTo + $testPermsFlagsArgs['OnlyApplyToChildren'] = $OnlyApplyToChildren + } + else { - $certPath = Join-Path -Path 'cert:' -ChildPath ($certificate.PSPath | Split-Path -NoQualifier) - $subject = $certificate.Subject - $thumbprint = $certificate.Thumbprint - if( -not $certificate.HasPrivateKey ) + $inheritanceFlags = [InheritanceFlags]::None + $propagationFlags = [PropagationFlags]::None + if($PSBoundParameters.ContainsKey('ApplyTo') -or $PSBoundParameters.ContainsKey('OnlyApplyToChildren')) { - $msg = "Unable to grant permission to ${subject} (thumbprint: ${thumbprint}; path ${certPath}) " + - 'certificate''s private key because that certificate doesn''t have a private key.' + $msg = "Failed to set ""applies to"" flags on path ""${currentPath}"" because it is a file. Please " + + 'omit "ApplyTo" and "OnlyApplyToChildren" parameters when granting permissions on a file.' Write-Warning $msg - return } + } - if (-not $Description) - { - $Description = "${certPath} ${subject}" - } + if (-not $Description) + { + $Description = $currentPath + } - if (-not $certificate.PrivateKey -or ` - -not ($certificate.PrivateKey | Get-Member -Name 'CspKeyContainerInfo')) + $rulesToRemove = $null + if( $Clear ) + { + $rulesToRemove = + $currentAcl.Access | + Where-Object { $_.IdentityReference.Value -ne $accountName } | + # Don't remove Administrators access. + Where-Object { $_.IdentityReference.Value -ne 'BUILTIN\Administrators' } | + Where-Object { -not $_.IsInherited } + + if( $rulesToRemove ) { - $privateKeyFilePaths = $certificate | Resolve-CPrivateKeyPath - if( -not $privateKeyFilePaths ) - { - # Resolve-CPrivateKeyPath writes an appropriately detailed error message. - continue - } - - $grantPermArgs = New-Object -TypeName 'Collections.Generic.Dictionary[[String], [Object]]' ` - -ArgumentList $PSBoundParameters - [void]$grantPermArgs.Remove('Path') - [void]$grantPermArgs.Remove('Permission') - - foreach ($privateKeyFile in $privateKeyFilePaths) + foreach( $ruleToRemove in $rulesToRemove ) { - Grant-CPermission -Path $privateKeyFile -Permission $rights @grantPermArgs -Description $Description + $rmType = $ruleToRemove.AccessControlType.ToString().ToLowerInvariant() + $rmRights = $ruleToRemove."${providerName}Rights" + Write-Information "${Description} ${Identity} - ${rmType} ${rmRights}" + [void]$currentAcl.RemoveAccessRule( $ruleToRemove ) } - continue } + } - [Security.AccessControl.CryptoKeySecurity]$keySecurity = - $certificate.PrivateKey.CspKeyContainerInfo.CryptoKeySecurity - if (-not $keySecurity) - { - $msg = "Failed to grant permission to ${subject} (thumbprint: ${thumbprint}; path: ${certPath}) " + - 'certificate''s private key because the private key has no security information.' - Write-Error -Message $msg -ErrorAction $ErrorActionPreference - continue - } - $rulesToRemove = @() - if ($Clear) - { - $rulesToRemove = - $keySecurity.Access | - Where-Object { $_.IdentityReference.Value -ne $Identity } | - # Don't remove Administrators access. - Where-Object { $_.IdentityReference.Value -ne 'BUILTIN\Administrators' } - if ($rulesToRemove) - { - foreach ($ruleToRemove in $rulesToRemove) - { - $rmIdentity = $ruleToRemove.IdentityReference.ToString() - $rmType = $ruleToRemove.AccessControlType.ToString().ToLowerInvariant() - $rmRights = $ruleToRemove.CryptoKeyRights - Write-Information "${Description} ${rmIdentity} - ${rmType} ${rmRights}" - if (-not $keySecurity.RemoveAccessRule($ruleToRemove)) - { - $msg = "Failed to remove ""${rmIdentity}"" identity's ${rmType} ""${rmRights}"" " + - "permissions to ${subject} (thumbprint: ${thumbprint}; path: ${certPath}) " + - 'certificates''s private key.' - Write-Error -Message $msg -ErrorAction $ErrorActionPreference - continue - } - } - } - } + $accessRule = + New-Object -TypeName "Security.AccessControl.${providerName}AccessRule" ` + -ArgumentList $accountName,$rights,$inheritanceFlags,$propagationFlags,$Type | + Add-Member -MemberType NoteProperty -Name 'Path' -Value $currentPath -PassThru - $accessRule = - New-Object -TypeName 'Security.AccessControl.CryptoKeyAccessRule' ` - -ArgumentList $Identity, $rights, $Type | - Add-Member -MemberType NoteProperty -Name 'Path' -Value $certPath -PassThru + $missingPermission = -not (Test-CPermission -Path $currentPath ` + -Identity $accountName ` + -Permission $Permission ` + @testPermsFlagsArgs ` + -Strict) - if ($Force -or ` - $rulesToRemove -or ` - -not (Test-CPermission -Path $certPath -Identity $Identity -Permission $Permission -Strict)) + $setAccessRule = ($Force -or $missingPermission) + if( $setAccessRule ) + { + if( $Append ) { - $currentPerm = Get-CPermission -Path $certPath -Identity $Identity - if ($currentPerm) - { - $curType = $currentPerm.AccessControlType.ToString().ToLowerInvariant() - $curRights = $currentPerm."$($providerName)Rights" - Write-Information "${Description} ${Identity} - ${curType} ${curRights}" - } - $newType = $Type.ToString().ToLowerInvariant() - Write-Information "${Description} ${Identity} + ${newType} ${rights}" - $keySecurity.SetAccessRule($accessRule) - $action = "grant ""${Identity} ${newType} ${rights} permission(s)" - Set-CCryptoKeySecurity -Certificate $certificate -CryptoKeySecurity $keySecurity -Action $action + $currentAcl.AddAccessRule( $accessRule ) } - - if( $PassThru ) + else { - return $accessRule + $currentAcl.SetAccessRule( $accessRule ) } } - return - } - - # We don't use Get-Acl because it returns the whole security descriptor, which includes owner information. When - # passed to Set-Acl, this causes intermittent errors. So, we just grab the ACL portion of the security - # descriptor. See - # http://www.bilalaslam.com/2010/12/14/powershell-workaround-for-the-security-identifier-is-not-allowed-to-be-the-owner-of-this-object-with-set-acl/ - $currentAcl = Get-Item -Path $Path -Force | Get-CAcl -IncludeSection ([AccessControlSections]::Access) - if (-not $ApplyTo) - { - $ApplyTo = 'ContainerSubcontainersAndLeaves' - } - $flags = ConvertTo-Flags -ApplyTo $ApplyTo -OnlyApplyToChildren:$OnlyApplyToChildren - - $testPermsFlagsArgs = @{ } - if (Test-Path $Path -PathType Container) - { - $testPermsFlagsArgs['ApplyTo'] = $ApplyTo - $testPermsFlagsArgs['OnlyApplyToChildren'] = $OnlyApplyToChildren - } - else - { - $flags.InheritanceFlags = [InheritanceFlags]::None - $flags.PropagationFlags = [PropagationFlags]::None - if($PSBoundParameters.ContainsKey('ApplyTo') -or $PSBoundParameters.ContainsKey('OnlyApplyToChildren')) - { - $msg = 'Can''t set "applies to" flags on a leaf. Please omit "ApplyTo" and "OnlyApplyToChildren" ' + - 'parameters when "Path" is a leaf.' - Write-Warning $msg - } - } - - if (-not $Description) - { - $Description = $Path - } - - $rulesToRemove = $null - $Identity = Resolve-CIdentityName -Name $Identity - if( $Clear ) - { - $rulesToRemove = $currentAcl.Access | - Where-Object { $_.IdentityReference.Value -ne $Identity } | - # Don't remove Administrators access. - Where-Object { $_.IdentityReference.Value -ne 'BUILTIN\Administrators' } | - Where-Object { -not $_.IsInherited } - - if( $rulesToRemove ) + if ($rulesToRemove -or $setAccessRule) { - foreach( $ruleToRemove in $rulesToRemove ) + $currentPerm = Get-CPermission -Path $currentPath -Identity $accountName + $curRights = 0 + $curType = '' + $curIdentity = $accountName + if ($currentPerm) { - $rmType = $ruleToRemove.AccessControlType.ToString().ToLowerInvariant() - $rmRights = $ruleToRemove."${providerName}Rights" - Write-Information "${Description} ${Identity} - ${rmType} ${rmRights}" - [void]$currentAcl.RemoveAccessRule( $ruleToRemove ) + $curType = $currentPerm.AccessControlType.ToString().ToLowerInvariant() + $curRights = $currentPerm."${providerName}Rights" + $curIdentity = $currentPerm.IdentityReference } - } - } - - - $accessRule = - New-Object -TypeName "Security.AccessControl.$($providerName)AccessRule" ` - -ArgumentList $Identity,$rights,$flags.InheritanceFlags,$flags.PropagationFlags,$Type | - Add-Member -MemberType NoteProperty -Name 'Path' -Value $Path -PassThru - - $missingPermission = - -not (Test-CPermission -Path $Path -Identity $Identity -Permission $Permission @testPermsFlagsArgs -Strict) - - $setAccessRule = ($Force -or $missingPermission) - if( $setAccessRule ) - { - if( $Append ) - { - $currentAcl.AddAccessRule( $accessRule ) - } - else - { - $currentAcl.SetAccessRule( $accessRule ) - } - } - - if ($rulesToRemove -or $setAccessRule) - { - $currentPerm = Get-CPermission -Path $Path -Identity $Identity - $curRights = 0 - $curType = '' - $curIdentity = $Identity - if ($currentPerm) - { - $curType = $currentPerm.AccessControlType.ToString().ToLowerInvariant() - $curRights = $currentPerm."$($providerName)Rights" - $curIdentity = $currentPerm.IdentityReference - } - $newType = $accessRule.AccessControlType.ToString().ToLowerInvariant() - $newRights = $accessRule."${providerName}Rights" - $newIdentity = $accessRule.IdentityReference - if ($Append) - { - Write-Information "${Description} ${newIdentity} + ${newType} ${newRights}" - } - else - { - if ($currentPerm) + $newType = $accessRule.AccessControlType.ToString().ToLowerInvariant() + $newRights = $accessRule."${providerName}Rights" + $newIdentity = $accessRule.IdentityReference + if ($Append) { - Write-Information "${Description} ${curIdentity} - ${curType} ${curRights}" + Write-Information "${Description} ${newIdentity} + ${newType} ${newRights}" + } + else + { + if ($currentPerm) + { + Write-Information "${Description} ${curIdentity} - ${curType} ${curRights}" + } + Write-Information "${Description} ${newIdentity} + ${newType} ${newRights}" } - Write-Information "${Description} ${newIdentity} + ${newType} ${newRights}" + Set-Acl -Path $currentPath -AclObject $currentAcl } - Set-Acl -Path $Path -AclObject $currentAcl - } - if( $PassThru ) - { - return $accessRule + if( $PassThru ) + { + $accessRule | Write-Output + } } } diff --git a/Carbon.Permissions/Functions/Resolve-Arg.ps1 b/Carbon.Permissions/Functions/Resolve-Arg.ps1 new file mode 100644 index 0000000..dc5d561 --- /dev/null +++ b/Carbon.Permissions/Functions/Resolve-Arg.ps1 @@ -0,0 +1,192 @@ + +function Resolve-Arg +{ + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [String] $Path, + + [String] $Identity, + + [String[]] $Permission, + + [String] $ApplyTo, + + [switch] $OnlyApplyToChildren, + + [Parameter(Mandatory)] + [ValidateSet('get', 'grant', 'revoke', 'test')] + [String] $Action + ) + + Set-StrictMode -Version 'Latest' + Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState + + $result = [pscustomobject]@{ + Paths = @(); + AccountName = ''; + Rights = 0x0; + ProviderName = ''; + InheritanceFlags = [InheritanceFlags]::None; + PropagationFlags = [PropagationFlags]::None; + } + + $permsMsg = ' permissions' + if ($Permission) + { + $permsMsg = " $($Permission -join ',') permissions" + } + + $accountMsg = '' + if ($Identity) + { + if (-not (Test-CIdentity -Name $Identity)) + { + $msg = "Failed to ${Action}${permsMsg} on path ""${Path}"" to account ""${Identity}"" because that " + + 'account does not exist.' + Write-Error -Message $msg -ErrorAction $ErrorActionPreference + return + } + + $accountName = $result.AccountName = Resolve-CIdentityName -Name $Identity + $accountMsg = " account ""${accountName}""" + + if ($Permission) + { + $accountMsg = " ""${accountName}"" account's" + } + } + + if (-not (Test-Path -Path $Path)) + { + $msg = "Failed to ${Action}${accountMsg}${permsMsg} on path ""${Path}"" because that path does not exist." + Write-Error -Message $msg -ErrorAction $ErrorActionPreference + return $false + } + + $result.Paths = $Path | Resolve-Path + + $providerName = Get-CPathProvider -Path $Path | Select-Object -ExpandProperty 'Name' + if (-not $providerName) + { + $msg = "Failed to ${Action}${accountMsg}${permsMsg} on path ""${Path}"" because that path has an unknown " + + 'provider.' + Write-Error -Message $msg -ErrorAction $ErrorActionPreference + return + } + + if ($providerName -ne 'Registry' -and $providerName -ne 'FileSystem') + { + $msg = "Failed to ${Action}${accountMsg}${permsMsg} on path ""${Path}"" because that path uses the " + + "unsupported ""${providerName}"" provider but only file system and registry paths are supported." + Write-Error -Message $msg -ErrorAction $ErrorActionPreference + return + } + $result.ProviderName = $providerName + + if ($Permission) + { + $rightTypeName = "Security.AccessControl.${providerName}Rights" + + $rights = 0 -as $rightTypeName + + foreach ($value in $Permission) + { + $right = $value -as $rightTypeName + if (-not $right) + { + $allowedValues = [Enum]::GetNames($rightTypeName) -join ', ' + $msg = "Failed to ${Action}${accountMsg} ""${value}"" permission because that permission is invalid " + + "or unknown. It must be a [${rightTypeName}] enumeration value: ${allowedValues}." + Write-Error -Message $msg -ErrorAction $ErrorActionPreference + return + } + + Write-Debug " ${value} → ${right}/0x$($right.ToString('x'))" + $rights = $rights -bor $right + } + + $result.Rights = $rights + } + + if ($ApplyTo) + { + # ApplyTo OnlyApplyToChildren InheritanceFlags PropagationFlags + # ------- ------------------- ---------------- ---------------- + # ContainerOnly true None None + # ContainerSubcontainersAndLeaves true ContainerInherit, ObjectInherit NoPropagateInherit + # ContainerAndSubcontainers true ContainerInherit NoPropagateInherit + # ContainerAndLeaves true ObjectInherit NoPropagateInherit + # SubcontainersAndLeavesOnly true ContainerInherit, ObjectInherit NoPropagateInherit, InheritOnly + # SubcontainersOnly true ContainerInherit NoPropagateInherit, InheritOnly + # LeavesOnly true ObjectInherit NoPropagateInherit, InheritOnly + # ContainerOnly false None None + # ContainerSubcontainersAndLeaves false ContainerInherit, ObjectInherit None + # ContainerAndSubcontainers false ContainerInherit None + # ContainerAndLeaves false ObjectInherit None + # SubcontainersAndLeavesOnly false ContainerInherit, ObjectInherit InheritOnly + # SubcontainersOnly false ContainerInherit InheritOnly + # LeavesOnly false ObjectInherit InheritOnly + + $inheritanceFlags = [InheritanceFlags]::None + $propagationFlags = [PropagationFlags]::None + + switch ($ApplyTo) + { + 'ContainerOnly' + { + $inheritanceFlags = [InheritanceFlags]::None + $propagationFlags = [PropagationFlags]::None + } + 'ContainerSubcontainersAndLeaves' + { + $inheritanceFlags = [InheritanceFlags]::ContainerInherit -bor [InheritanceFlags]::ObjectInherit + $propagationFlags = [PropagationFlags]::None + } + 'ContainerAndSubcontainers' + { + $inheritanceFlags = [InheritanceFlags]::ContainerInherit + $propagationFlags = [PropagationFlags]::None + } + 'ContainerAndLeaves' + { + $inheritanceFlags = [InheritanceFlags]::ObjectInherit + $propagationFlags = [PropagationFlags]::None + } + 'SubcontainersAndLeavesOnly' + { + $inheritanceFlags = [InheritanceFlags]::ContainerInherit -bor [InheritanceFlags]::ObjectInherit + $propagationFlags = [PropagationFlags]::InheritOnly + } + 'SubcontainersOnly' + { + $inheritanceFlags = [InheritanceFlags]::ContainerInherit + $propagationFlags = [PropagationFlags]::InheritOnly + } + 'LeavesOnly' + { + $inheritanceFlags = [InheritanceFlags]::ObjectInherit + $propagationFlags = [PropagationFlags]::InheritOnly + } + default + { + $msg = "Failed to ${Action}${accountMsg}${permsMsg} on path ""${Path}"" because the ""AppliesTo"" " + + "parameter ""${ApplyTo}"" is invalid or unknown. Supported values are ""ContainerOnly, " + + 'ContainerSubcontainersAndLeaves, ContainerAndSubcontainers, ContainerAndLeaves, ' + + 'SubcontainersAndLeavesOnly, SubcontainersOnly, LeavesOnly"".' + Write-Error -Message $msg -ErrorAction $ErrorActionPreference + return + } + } + + if ($OnlyApplyToChildren -and $ApplyTo -ne 'ContainerOnly') + { + $propagationFlags = $propagationFlags -bor [PropagationFlags]::NoPropagateInherit + } + + $result.InheritanceFlags = $inheritanceFlags + $result.PropagationFlags = $propagationFlags + } + + return $result +} \ No newline at end of file diff --git a/Carbon.Permissions/Functions/Resolve-CPrivateKeyPath.ps1 b/Carbon.Permissions/Functions/Resolve-CPrivateKeyPath.ps1 deleted file mode 100644 index 9148458..0000000 --- a/Carbon.Permissions/Functions/Resolve-CPrivateKeyPath.ps1 +++ /dev/null @@ -1,205 +0,0 @@ - -function Resolve-CPrivateKeyPath -{ - <# - .SYNOPSIS - Finds the path to a certificate private key. - - .DESCRIPTION - The `Resolve-CPrivateKeyPath` function finds the path to a certificate private key. Pipe the certificate object to - the function (or pass one or more to the `Certificate` parameter). The function searches all the directories where - keys are stored, [which are documented by - Microsoft](https://learn.microsoft.com/en-us/windows/win32/seccng/key-storage-and-retrieval). - - If the certificate doesn't have a private key, have access to the private key, or no private key file exists, the - function writes an error and returns nothing for that certificate. - - Returns the path to the private key as a string. - - .LINK - https://learn.microsoft.com/en-us/windows/win32/seccng/key-storage-and-retrieval - - .EXAMPLE - $cert | Resolve-CPrivateKeyPath - - Demonstrates that you can pipe X509Certificate2 objects to this function. - - .EXAMPLE - Resolve-CPrivateKeyPath -Certificate $cert - - Demonstrates that you pass an X509Certificate2 object to the `Certificate` parameter. - #> - [CmdletBinding()] - [OutputType([String])] - param( - # The certificate whose private key path to get. Must have a private key and that private key must be accessible - # by the current user. - [Parameter(Mandatory, ValueFromPipeline)] - [Security.Cryptography.X509Certificates.X509Certificate2[]] $Certificate - ) - - begin - { - Set-StrictMode -Version 'Latest' - Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState - - $searchPaths = - & { - $appData = [Environment]::GetFolderPath('ApplicationData') - if ($appData) - { - if ($IsWindows) - { - $sid = [Security.Principal.WindowsIdentity]::GetCurrent().User - $sidString = $sid.ToString() - - # CSP user private - Join-Path -Path $appData -ChildPath "Microsoft\Crypto\RSA\${sidString}" - Join-Path -Path $appData -ChildPath "Microsoft\Crypto\DSS\${sidString}" - } - - # CNG user private - Join-Path -Path $appData -ChildPath "Microsoft\Crypto\Keys" - } - - $commonAppDataPath = [Environment]::GetFolderPath('CommonApplicationData') - if ($commonAppDataPath) - { - # CSP local system private - Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\RSA\S-1-5-18' - Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\DSS\S-1-5-18' - - # CNG local system private - Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\SystemKeys' - - # CSP local service private - Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\RSA\S-1-5-19' - Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\DSS\S-1-5-19' - - # CSP network service private - Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\RSA\S-1-5-20' - Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\DSS\S-1-5-20' - - # CSP shared private - Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\RSA\MachineKeys' - Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\DSS\MachineKeys' - - # CNG shared private - Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\Keys' - } - - $windowsPath = [Environment]::GetFolderPath('Windows') - if ($windowsPath) - { - # CNG local service private - Join-Path -Path $windowsPath -ChildPath 'ServiceProfiles\LocalService\AppData\Roaming\Microsoft\Crypto\Keys' - - # CNG network service private - Join-Path -Path $windowsPath -ChildPath 'ServiceProfiles\NetworkService\AppData\Roaming\Microsoft\Crypto\Keys' - } - } | - Where-Object { $_ } - - $accessibleSearchPaths = $searchPaths | Where-Object { Test-Path -Path $_ -ErrorAction Ignore } - } - - process - { - $foundOne = $false - foreach ($cert in $Certificate) - { - $certErrMsg = "Failed to find the path to the ""$($certificate.Subject)"" ($($certificate.Thumbprint)) " + - 'certificate''s private key because ' - if (-not $cert.HasPrivateKey) - { - $msg = "${certErrMsg}it does not have a private key." - Write-Error -Message $msg -ErrorAction $ErrorActionPreference - continue - } - - $privateKey = $cert.PrivateKey - if (-not $privateKey) - { - try - { - $privateKey = - [Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert) - } - catch - { - $msg = "$($certErrMsg -replace ' because ', ': ') ${_}." - Write-Error -Message $msg -ErrorAction $ErrorActionPreference - continue - } - - if (-not $privateKey) - { - $msg = "${certErrMsg}the current user doesn't have permission to the private key." - Write-Error -Message $msg -ErrorAction $ErrorActionPreference - continue - } - } - - $fileName = '' - if ($privateKey | Get-Member -Name 'CspKeyContainerInfo') - { - $fileName = $privateKey.CspKeyContainerInfo.UniqueKeyContainerName - } - elseif ($privateKey | Get-Member -Name 'Key') - { - $fileName = $privateKey.Key.UniqueName - } - - if (-not $fileName) - { - $msg = "${certErrMsg}is of type [$($privateKey.GetType().FullName)], which is not currently " + - 'supported by Carbon. [Please request support by submitting an issue on the project''s ' + - 'GitHub issues page.](https://github.com/webmd-health-services/Carbon.Cryptography/issues/new)' - Write-Error -Message $msg -ErrorAction $ErrorActionPreference - continue - } - - $foundOne = $false - $uniqueNameIsPath = $false - if ($fileName | Split-Path) - { - $uniqueNameIsPath = $true - if ((Test-Path -Path $fileName -PathType Leaf -ErrorAction Ignore)) - { - $foundOne = $true - $fileName | Write-Output - } - } - else - { - foreach ($path in $accessibleSearchPaths) - { - $fullPath = Join-Path -Path $path -ChildPath $fileName - if (-not (Test-Path -Path $fullPath -PathType Leaf -ErrorAction Ignore)) - { - continue - } - $foundOne = $true - $fullPath | Write-Output - } - } - - if (-not $foundOne) - { - if ($uniqueNameIsPath) - { - $msg = "${certErrMsg}its file, ""${fileName}"", doesn't exist." - } - else - { - $msg = "${certErrMsg}its file, ""${fileName}"", doesn't exist in any of these " + - "directories:" + [Environment]::NewLine + - " " + [Environment]::NewLine + - "* $($searchPaths -join "$([Environment]::NewLine)* ")" - } - Write-Error -Message $msg -ErrorAction $ErrorActionPreference - continue - } - } - } -} \ No newline at end of file diff --git a/Carbon.Permissions/Functions/Revoke-CPermission.ps1 b/Carbon.Permissions/Functions/Revoke-CPermission.ps1 index 2744a73..06fcb25 100644 --- a/Carbon.Permissions/Functions/Revoke-CPermission.ps1 +++ b/Carbon.Permissions/Functions/Revoke-CPermission.ps1 @@ -3,12 +3,11 @@ function Revoke-CPermission { <# .SYNOPSIS - Revokes permissions on a file, directory, registry key, or certificate private key/key container. + Revokes permissions on a file, directory, or registry keys. .DESCRIPTION The `Revoke-CPermission` function removes a user or group's *explicit, non-inherited* permissions on a file, - directory, registry key, or certificate private key/key container. Using this function and module are not - recommended. Instead, + directory, or registry key. Using this function and module are not recommended. Instead, * for file directory permissions, use `Revoke-CNtfsPermission` in the `Carbon.FileSystem` module. * for registry permissions, use `Revoke-CRegistryPermission` in the `Carbon.Registry` module. @@ -37,17 +36,11 @@ function Revoke-CPermission Revoke-CPermission -Identity ENTERPRISE\Interns -Path 'hklm:\system\WarpDrive' Demonstrates how to revoke permission on a registry key. - - .EXAMPLE - Revoke-CPermission -Identity ENTERPRISE\Officers -Path 'cert:\LocalMachine\My\1234567890ABCDEF1234567890ABCDEF12345678' - - Demonstrates how to revoke the Officers' permission to the - `cert:\LocalMachine\My\1234567890ABCDEF1234567890ABCDEF12345678` certificate's private key/key container. #> + [Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '')] [CmdletBinding(SupportsShouldProcess)] param( - # The path on which the permissions should be revoked. Can be a file system, registry, or certificate path. For - # certificate private keys, pass a certificate provider path, e.g. `cert:`. + # The path on which the permissions should be revoked. Can be a file system or registry path. [Parameter(Mandatory)] [String] $Path, @@ -62,108 +55,48 @@ function Revoke-CPermission Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState - $Path = Resolve-Path -Path $Path - if( -not $Path ) + $rArgs = Resolve-Arg -Path $Path -Identity $Identity -Action 'revoke' + if (-not $rArgs) { return } - $providerName = Get-CPathProvider -Path $Path | Select-Object -ExpandProperty 'Name' - if( $providerName -eq 'Certificate' ) - { - $providerName = 'CryptoKey' - if( -not (Test-CCryptoKeyAvailable) ) - { - $providerName = 'FileSystem' - } - } + $accountName = $rArgs.AccountName - $rulesToRemove = Get-CPermission -Path $Path -Identity $Identity + $rulesToRemove = Get-CPermission -Path $Path -Identity $accountName if (-not $rulesToRemove) { return } - $Identity = Resolve-CIdentityName -Name $Identity + $providerName = $rArgs.ProviderName - foreach ($item in (Get-Item $Path -Force)) + foreach ($currentPath in $rArgs.Paths) { - if( $item.PSProvider.Name -ne 'Certificate' ) - { - if (-not $Description) - { - $Description = $item.ToString() - } - - # We don't use Get-Acl because it returns the whole security descriptor, which includes owner information. - # When passed to Set-Acl, this causes intermittent errors. So, we just grab the ACL portion of the security - # descriptor. See - # http://www.bilalaslam.com/2010/12/14/powershell-workaround-for-the-security-identifier-is-not-allowed-to-be-the-owner-of-this-object-with-set-acl/ - $currentAcl = $item | Get-CAcl -IncludeSection ([AccessControlSections]::Access) - - foreach ($ruleToRemove in $rulesToRemove) - { - $rmIdentity = $ruleToRemove.IdentityReference - $rmType = $ruleToRemove.AccessControlType.ToString().ToLowerInvariant() - $rmRights = $ruleToRemove."${providerName}Rights" - Write-Information "${Description} ${rmIdentity} - ${rmType} ${rmRights}" - [void]$currentAcl.RemoveAccessRule($ruleToRemove) - } - if( $PSCmdlet.ShouldProcess( $Path, ('revoke {0}''s permissions' -f $Identity)) ) - { - Set-Acl -Path $Path -AclObject $currentAcl - } - continue - } - - $certMsg = """$($item.Subject)"" (thumbprint: $($item.Thumbprint); path: " + - "cert:\$($item.PSPath | Split-Path -NoQualifier)) " - if (-not $item.HasPrivateKey) - { - Write-Verbose -Message "Skipping certificate ${certMsg}because it doesn't have a private key." - continue - } - if (-not $Description) { - $Description = "cert:\$($item.PSPath | Split-Path -NoQualifier) ($($item.Thumbprint))" + $Description = $currentPath } - $privateKey = $item.PrivateKey - if ($privateKey -and ($item.PrivateKey | Get-Member 'CspKeyContainerInfo')) - { - [Security.Cryptography.X509Certificates.X509Certificate2]$certificate = $item - - [Security.AccessControl.CryptoKeySecurity]$keySecurity = - $certificate.PrivateKey.CspKeyContainerInfo.CryptoKeySecurity - - foreach ($ruleToRemove in $rulesToRemove) - { - $rmIdentity = $ruleToRemove.IdentityReference - $rmType = $ruleToRemove.AccessControlType.ToString().ToLowerInvariant() - $rmRights = $ruleToRemove."${providerName}Rights" - Write-Information "${Description} ${rmIdentity} - ${rmType} ${rmRights}" - [void] $keySecurity.RemoveAccessRule($ruleToRemove) - } - - $action = "revoke ${Identity}'s permissions" - Set-CCryptoKeySecurity -Certificate $certificate -CryptoKeySecurity $keySecurity -Action $action - return - } + # We don't use Get-Acl because it returns the whole security descriptor, which includes owner information. + # When passed to Set-Acl, this causes intermittent errors. So, we just grab the ACL portion of the security + # descriptor. See + # http://www.bilalaslam.com/2010/12/14/powershell-workaround-for-the-security-identifier-is-not-allowed-to-be-the-owner-of-this-object-with-set-acl/ + $currentAcl = + Get-Item -LiteralPath $currentPath -Force | Get-CAcl -IncludeSection ([AccessControlSections]::Access) - $privateKeyFilesPaths = $item | Resolve-CPrivateKeyPath - if (-not $privateKeyFilesPaths) + foreach ($ruleToRemove in $rulesToRemove) { - # Resolve-CPrivateKeyPath writes an appropriately detailed error message. - continue + $rmIdentity = $ruleToRemove.IdentityReference + $rmType = $ruleToRemove.AccessControlType.ToString().ToLowerInvariant() + $rmRights = $ruleToRemove."${providerName}Rights" + Write-Information "${Description} ${rmIdentity} - ${rmType} ${rmRights}" + [void]$currentAcl.RemoveAccessRule($ruleToRemove) } - $revokePermissionParams = New-Object -TypeName 'Collections.Generic.Dictionary[[string], [object]]' ` - -ArgumentList $PSBoundParameters - [void]$revokePermissionParams.Remove('Path') - foreach( $privateKeyFilePath in $privateKeyFilesPaths ) + if ($PSCmdlet.ShouldProcess($currentPath, "revoke ""${accountName}"" account's permissions")) { - Revoke-CPermission -Path $privateKeyFilePath @revokePermissionParams -Description $Description + Set-Acl -Path $currentPath -AclObject $currentAcl } } } diff --git a/Carbon.Permissions/Functions/Set-CCryptoKeySecurity.ps1 b/Carbon.Permissions/Functions/Set-CCryptoKeySecurity.ps1 deleted file mode 100644 index ab37850..0000000 --- a/Carbon.Permissions/Functions/Set-CCryptoKeySecurity.ps1 +++ /dev/null @@ -1,46 +0,0 @@ - -function Set-CCryptoKeySecurity -{ - [CmdletBinding()] - param( - [Parameter(Mandatory)] - [Security.Cryptography.X509Certificates.X509Certificate2] $Certificate, - - [Parameter(Mandatory)] - [Security.AccessControl.CryptoKeySecurity] $CryptoKeySecurity, - - [Parameter(Mandatory)] - [String] $Action - ) - - Set-StrictMode -Version 'Latest' - Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState - - $keyContainerInfo = $Certificate.PrivateKey.CspKeyContainerInfo - $cspParams = New-Object 'Security.Cryptography.CspParameters' ($keyContainerInfo.ProviderType, $keyContainerInfo.ProviderName, $keyContainerInfo.KeyContainerName) - $cspParams.Flags = [Security.Cryptography.CspProviderFlags]::UseExistingKey - $cspParams.KeyNumber = $keyContainerInfo.KeyNumber - if( (Split-Path -NoQualifier -Path $Certificate.PSPath) -like 'LocalMachine\*' ) - { - $cspParams.Flags = $cspParams.Flags -bor [Security.Cryptography.CspProviderFlags]::UseMachineKeyStore - } - $cspParams.CryptoKeySecurity = $CryptoKeySecurity - - try - { - # persist the rule change - if( $PSCmdlet.ShouldProcess( ('{0} ({1})' -f $Certificate.Subject,$Certificate.Thumbprint), $Action ) ) - { - $null = New-Object 'Security.Cryptography.RSACryptoServiceProvider' ($cspParams) - } - } - catch - { - $actualException = $_.Exception - while( $actualException.InnerException ) - { - $actualException = $actualException.InnerException - } - Write-Error ('Failed to {0} to ''{1}'' ({2}) certificate''s private key: {3}: {4}' -f $Action,$Certificate.Subject,$Certificate.Thumbprint,$actualException.GetType().FullName,$actualException.Message) - } -} diff --git a/Carbon.Permissions/Functions/Test-CCryptoKeyAvailable.ps1 b/Carbon.Permissions/Functions/Test-CCryptoKeyAvailable.ps1 deleted file mode 100644 index 95ba667..0000000 --- a/Carbon.Permissions/Functions/Test-CCryptoKeyAvailable.ps1 +++ /dev/null @@ -1,4 +0,0 @@ -function Test-CCryptoKeyAvailable -{ - return $null -ne [Type]::GetType('System.Security.AccessControl.CryptoKeyRights') -} \ No newline at end of file diff --git a/Carbon.Permissions/Functions/Test-CPermission.ps1 b/Carbon.Permissions/Functions/Test-CPermission.ps1 index 55950c1..583c814 100644 --- a/Carbon.Permissions/Functions/Test-CPermission.ps1 +++ b/Carbon.Permissions/Functions/Test-CPermission.ps1 @@ -3,11 +3,11 @@ function Test-CPermission { <# .SYNOPSIS - Tests permissions on a file, directory, registry key, or certificate private key/key container. + Tests permissions on a file, directory, or registry key .DESCRIPTION - The `Test-CPermission` function tests if permissions are granted to a user or group on a file, directory, registry - key, or certificate private key/key container. Using this function and module are not recommended. Instead, + The `Test-CPermission` function tests if permissions are granted to a user or group on a file, directory, or + registry key. Using this function and module are not recommended. Instead, * for file directory permissions, use `Test-CNtfsPermission` in the `Carbon.FileSystem` module. * for registry permissions, use `Test-CRegistryPermission` in the `Carbon.Registry` module. @@ -19,15 +19,12 @@ function Test-CPermission function returns `true`. Otherwise it returns `false`. The `Permissions` attribute should be a list of - [FileSystemRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx), - [RegistryRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx), or - [CryptoKeyRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.cryptokeyrights.aspx), for - files/directories, registry keys, and certificate private keys, respectively. These commands will show you the - values for the appropriate permissions for your object: + [FileSystemRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx) or + [RegistryRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx). These + commands will show you the values for the appropriate permissions for your object: [Enum]::GetValues([Security.AccessControl.FileSystemRights]) [Enum]::GetValues([Security.AccessControl.RegistryRights]) - [Enum]::GetValues([Security.AccessControl.CryptoKeyRights]) Extra/additional permissions on the item are ignored. To check that the user/group has the exact permissions passed to the `Permission` parameter, use the `Strict` switch. @@ -54,7 +51,6 @@ function Test-CPermission | SubcontainersOnly | true | ContainerInherit | NoPropagateInherit, InheritOnly | LeavesOnly | true | ObjectInherit | NoPropagateInherit, InheritOnly - By default, inherited permissions are ignored. To check inherited permission, use the `-Inherited` switch. .OUTPUTS @@ -75,9 +71,6 @@ function Test-CPermission .LINK http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx - .LINK - http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.cryptokeyrights.aspx - .EXAMPLE Test-CPermission -Identity 'STARFLEET\JLPicard' -Permission 'FullControl' -Path 'C:\Enterprise\Bridge' @@ -92,17 +85,10 @@ function Test-CPermission Test-CPermission -Identity 'STARFLEET\Worf' -Permission 'Write' -ApplyTo 'Container' -Path 'C:\Enterprise\Brig' Demonstrates how to test for inheritance/propogation flags, in addition to permissions. - - .EXAMPLE - Test-CPermission -Identity 'STARFLEET\Data' -Permission 'GenericWrite' -Path 'cert:\LocalMachine\My\1234567890ABCDEF1234567890ABCDEF12345678' - - Demonstrates how to test for permissions on a certificate's private key/key container. If the certificate doesn't - have a private key, returns `$true`. #> [CmdletBinding(DefaultParameterSetName='ExcludeApplyTo')] param( - # The path on which the permissions should be checked. Can be a file system or registry path. For certificate - # private keys, pass a certificate provider path, e.g. `cert:`. + # The path on which the permissions should be checked. Can be a file system or registry path. [Parameter(Mandatory)] [String] $Path, @@ -139,94 +125,69 @@ function Test-CPermission Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState - $originalPath = $Path - $Path = Resolve-Path -Path $Path -ErrorAction 'SilentlyContinue' - if( -not $Path -or -not (Test-Path -Path $Path) ) + $rArgs = Resolve-Arg -Path $Path ` + -Identity $Identity ` + -Permission $Permission ` + -ApplyTo $ApplyTo ` + -OnlyApplyToChildren:$OnlyApplyToChildren ` + -Action 'test' + if (-not $rArgs) { - if( -not $Path ) - { - $Path = $originalPath - } - Write-Error ('Unable to test {0}''s {1} permissions: path ''{2}'' not found.' -f $Identity,($Permission -join ','),$Path) return } - $providerName = Get-CPathProvider -Path $Path | Select-Object -ExpandProperty 'Name' - if( $providerName -eq 'Certificate' ) - { - $providerName = 'CryptoKey' - # CryptoKey does not exist in .NET standard/core so we will have to use FileSystem instead - if( -not (Test-CCryptoKeyAvailable) ) - { - $providerName = 'FileSystem' - } - } + $providerName = $rArgs.ProviderName + $rights = $rArgs.Rights + $inheritanceFlags = $rArgs.InheritanceFlags + $propagationFlags = $rArgs.PropagationFlags - if (($providerName -eq 'FileSystem' -or $providerName -eq 'CryptoKey') -and $Strict) + if ($providerName -eq 'FileSystem' -and $Strict) { # Synchronize is always on and can't be turned off. - $Permission += 'Synchronize' - } - $rights = $Permission | ConvertTo-CProviderAccessControlRights -ProviderName $providerName - if( -not $rights ) - { - Write-Error ('Unable to test {0}''s {1} permissions on {2}: received an unknown permission.' -f $Identity,$Permission,$Path) - return + $rights = $rights -bor [FileSystemRights]::Synchronize } - $rightsPropertyName = "${providerName}Rights" - $isLeaf = (Test-Path -Path $Path -PathType Leaf) - - $testFlags = $PSCmdlet.ParameterSetName -eq 'IncludeApplyTo' - $flags = $null - if ($testFlags) + foreach ($currentPath in $rArgs.Paths) { - $flags = ConvertTo-Flags -ApplyTo $ApplyTo -OnlyApplyToChildren:$OnlyApplyToChildren - } + $isLeaf = (Test-Path -LiteralPath $currentPath -PathType Leaf) + $testFlags = $PSCmdlet.ParameterSetName -eq 'IncludeApplyTo' - if ($isLeaf -and $testFlags) - { - $msg = 'Can''t test "applies to" flags on a leaf. Please omit "ApplyTo" and "OnlyApplyToChildren" parameters ' + - 'when "Path" is a leaf.' - Write-Warning $msg - } - - if( $providerName -eq 'CryptoKey' ) - { - # If the certificate doesn't have a private key, return $true. - if( (Get-Item -Path $Path | Where-Object { -not $_.HasPrivateKey } ) ) + if ($isLeaf -and $testFlags) { - return $true + $msg = "Failed to test ""applies to"" flags on path ""${currentPath}"" because it is a file. Please omit " + + '"ApplyTo" and "OnlyApplyToChildren" parameters when testing permissions on a file.' + Write-Warning $msg } - } - - - $acl = - Get-CPermission -Path $Path -Identity $Identity -Inherited:$Inherited | - Where-Object 'AccessControlType' -eq 'Allow' | - Where-Object 'IsInherited' -eq $Inherited | - Where-Object { - if ($Strict) - { - return ($_.$rightsPropertyName -eq $rights) - } - return ($_.$rightsPropertyName -band $rights) -eq $rights - } | - Where-Object { - if ($isLeaf -or -not $testFlags) - { - return $true + $rightsPropertyName = "${providerName}Rights" + $acl = + Get-CPermission -Path $currentPath -Identity $Identity -Inherited:$Inherited | + Where-Object 'AccessControlType' -eq 'Allow' | + Where-Object 'IsInherited' -eq $Inherited | + Where-Object { + if ($Strict) + { + return ($_.$rightsPropertyName -eq $rights) + } + + return ($_.$rightsPropertyName -band $rights) -eq $rights + } | + Where-Object { + if ($isLeaf -or -not $testFlags) + { + return $true + } + + return $_.InheritanceFlags -eq $inheritanceFlags -and $_.PropagationFlags -eq $propagationFlags } - return $_.InheritanceFlags -eq $flags.InheritanceFlags -and $_.PropagationFlags -eq $flags.PropagationFlags + if ($acl) + { + $true | Write-Output + continue } - if ($acl) - { - return $true + $false | Write-Output } - - return $false } diff --git a/Tests/Carbon.PermissionsTestHelper/Carbon.PermissionsTestHelper.psm1 b/Tests/Carbon.PermissionsTestHelper/Carbon.PermissionsTestHelper.psm1 index cb16fa3..99a8091 100644 --- a/Tests/Carbon.PermissionsTestHelper/Carbon.PermissionsTestHelper.psm1 +++ b/Tests/Carbon.PermissionsTestHelper/Carbon.PermissionsTestHelper.psm1 @@ -1,14 +1,2 @@ -function Invoke-TestCCryptoKeyAvailable -{ - [CmdletBinding()] - param( - ) - - InModuleScope 'Carbon.Permissions' -ScriptBlock { - param( - ) - Test-CCryptoKeyAvailable - } -Parameters $PSBoundParameters -} \ No newline at end of file diff --git a/Tests/ConvertTo-CProviderAccessControlRights.Tests.ps1 b/Tests/ConvertTo-CProviderAccessControlRights.Tests.ps1 deleted file mode 100644 index 9ec7438..0000000 --- a/Tests/ConvertTo-CProviderAccessControlRights.Tests.ps1 +++ /dev/null @@ -1,42 +0,0 @@ - -#Requires -Version 5.1 -Set-StrictMode -Version 'Latest' - -BeforeAll { - Set-StrictMode -Version 'Latest' - - & (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Test.ps1' -Resolve) -} - -Describe 'ConvertTo-CProviderAccessControlRights' { - Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\Carbon.Permissions' -Resolve) -Verbose:$false - InModuleScope 'Carbon.Permissions' { - It 'should convert file system value' { - (ConvertTo-CProviderAccessControlRights -ProviderName 'FileSystem' -InputObject 'Read') | Should -Be ([Security.AccessControl.FileSystemRights]::Read) - } - - It 'should convert file system values' { - $expected = [Security.AccessControl.FileSystemRights]::Read -bor [Security.AccessControl.FileSystemRights]::Write - $actual = ConvertTo-CProviderAccessControlRights -ProviderName 'FileSystem' -InputObject 'Read','Write' - $actual | Should -Be $expected - } - - It 'should convert file system value from pipeline' { - $expected = [Security.AccessControl.FileSystemRights]::Read -bor [Security.AccessControl.FileSystemRights]::Write - $actual = 'Read','Write' | ConvertTo-CProviderAccessControlRights -ProviderName 'FileSystem' - $actual | Should -Be $expected - } - - It 'should convert registry value' { - $expected = [Security.AccessControl.RegistryRights]::Delete - $actual = 'Delete' | ConvertTo-CProviderAccessControlRights -ProviderName 'Registry' - $actual | Should -Be $expected - } - - It 'should handle invalid right name' { - $Global:Error.Clear() - (ConvertTo-CProviderAccessControlRights -ProviderName 'FileSystem' -InputObject 'BlahBlah','Read' -ErrorAction SilentlyContinue) | Should -BeNullOrEmpty - $Global:Error.Count | Should -Be 1 - } - } -} diff --git a/Tests/Get-CPermission.Tests.ps1 b/Tests/Get-CPermission.Tests.ps1 index 005406f..3f4515b 100644 --- a/Tests/Get-CPermission.Tests.ps1 +++ b/Tests/Get-CPermission.Tests.ps1 @@ -7,27 +7,10 @@ BeforeAll { & (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Test.ps1' -Resolve) - Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\PSModules\Carbon.Cryptography' -Resolve) ` - -Function ('Install-CCertificate', 'Uninstall-CCertificate') ` - -Global ` - -Verbose:$false - $script:user = 'CarbonTestUser1' $script:group1 = 'CarbonTestGroup1' $script:containerPath = $null $script:childPath = $null - - function Get-CertificateWithPrivateKey - { - Get-Item -Path 'Cert:\*\*' | - Where-Object 'Name' -NotIn @('UserDS') | # This store causes problems on PowerShell 7. - Get-ChildItem | - Where-Object 'PsIsContainer' -EQ $false | - Where-Object 'HasPrivateKey' -EQ $true | - Where-Object 'PrivateKey' -NE $null | - # Couldn't get perms on a cert with this usage. - Where-Object { -not ($_.EnhancedKeyUsageList | Where-Object 'FriendlyName' -EQ 'Smart Card Logon') } - } } Describe 'Get-CPermission' { @@ -90,58 +73,4 @@ Describe 'Get-CPermission' { $perms | Should -Not -BeNullOrEmpty $perms | Should -BeOfType [Security.AccessControl.RegistryAccessRule] } - - It 'should get private cert permission' { - $certs = Get-CertificateWithPrivateKey - foreach ($cert in $certs) - { - $expectedType = [Security.AccessControl.FileSystemAccessRule] - if ($cert.PrivateKey -and ` - ($cert.PrivateKey | Get-Member -Name 'CspKeyContainerInfo') -and ` - [Type]::GetType('System.Security.AccessControl.CryptoKeyAccessRule')) - { - $expectedType = [Security.AccessControl.CryptoKeyAccessRule] - } - $certPath = Join-Path -Path 'cert:' -ChildPath ($cert.PSPath | Split-Path -NoQualifier) - $numErrors = $Global:Error.Count - $perms = Get-CPermission -Path $certPath -Inherited -ErrorAction SilentlyContinue - if ($numErrors -ne $Global:Error.Count -and ` - ($Global:Error[0] -match '(keyset does not exist)|(Invalid provider type specified)')) - { - continue - } - $perms | Should -Not -BeNullOrEmpty -Because "${certPath} should have private key permissions" - $perms | Should -BeOfType $expectedType - } - } - - It 'should get specific identity cert permission' { - Get-CertificateWithPrivateKey | - ForEach-Object { Join-Path -Path 'cert:' -ChildPath (Split-Path -NoQualifier -Path $_.PSPath) } | - ForEach-Object { - [Object[]]$rules = Get-CPermission -Path $_ - foreach( $rule in $rules ) - { - [Object[]]$identityRule = Get-CPermission -Path $_ -Identity $rule.IdentityReference.Value - $identityRule | Should -Not -BeNullOrEmpty - $identityRule.Count | Should -BeLessOrEqual $rules.Count - } - } - } - - It 'gets permissions for cng private key' { - $certFilePath = Join-Path -Path $PSScriptRoot -ChildPath 'Certificates\CarbonRsaCng.pfx' -Resolve - $cert = Install-CCertificate -Path $certFilePath -StoreLocation CurrentUser -StoreName My -PassThru - try - { - $perms = - Get-CPermission -Path (Join-Path -Path 'cert:\CurrentUser\My' -ChildPath $cert.Thumbprint) -Inherited - $perms | Should -Not -BeNullOrEmpty - $perms | Should -BeOfType [Security.AccessControl.FileSystemAccessRule] - } - finally - { - Uninstall-CCertificate -Thumbprint $cert.Thumbprint -StoreLocation CurrentUser -StoreName My - } - } } diff --git a/Tests/Grant-CPermission.Tests.ps1 b/Tests/Grant-CPermission.Tests.ps1 index 71f734e..dff54ea 100644 --- a/Tests/Grant-CPermission.Tests.ps1 +++ b/Tests/Grant-CPermission.Tests.ps1 @@ -18,11 +18,6 @@ BeforeAll { -Function ('Resolve-CIdentityName') ` -Global ` -Verbose:$false - $psModulesPath = Join-Path -Path $PSScriptRoot -ChildPath '..\PSModules' -Resolve - Import-Module -Name (Join-Path -Path $psModulesPath -ChildPath 'Carbon.Cryptography' -Resolve) ` - -Function ('Install-CCertificate', 'Uninstall-CCertificate') ` - -Global ` - -Verbose:$false $script:testDirPath = $null $script:testNum = 0 @@ -32,7 +27,6 @@ BeforeAll { $script:user2 = 'CarbonGrantPerms2' $containerPath = $null $regContainerPath = $null - $script:privateKeyPath = Join-Path -Path $PSScriptRoot -ChildPath 'Certificates\CarbonTestPrivateKey.pfx' -Resolve function Assert-InheritanceFlags { @@ -168,14 +162,6 @@ BeforeAll { $expectedPermission = [RegistryRights]0 $rightsPropertyName = 'RegistryRights' } - elseif ($provider.Name -eq 'Certificate') - { - if ((Invoke-TestCCryptoKeyAvailable) -and $Is -isnot [FileSystemRights]) - { - $expectedPermission = [CryptoKeyRights]0 - $rightsPropertyName = 'CryptoKeyRights' - } - } $Is | ForEach-Object { $expectedPermission = $expectedPermission -bor $_ } if ($rightsPropertyName -eq 'FileSystemRights' -and $OfType -eq [AccessControlType]::Allow) @@ -239,10 +225,10 @@ Describe 'Grant-CPermission' { It 'when passing an invalid permission' { $path = New-TestFile - $error.Clear() $result = Grant-CPermission -Identity 'BUILTIN\Administrators' -Permission 'BlahBlahBlah' -Path $path -PassThru -ErrorAction SilentlyContinue $result | Should -BeNullOrEmpty - $error.Count | Should -Be 2 + $Global:Error.Count | Should -Be 1 + $Global:Error | Should -Match 'permission is invalid or unknown' } It 'when clearing existing permissions' { @@ -416,7 +402,6 @@ Describe 'Grant-CPermission' { } It 'when forcing a permission change and the user already has the permissions' { - $Global:VerbosePreference = $Global:DebugPreference = 'Continue' $containerPath = New-TestContainer -FileSystem Grant-CPermission -Identity $script:user ` @@ -456,8 +441,8 @@ Describe 'Grant-CPermission' { It 'when the path does not exist' { $result = Grant-CPermission -Identity $script:user -Permission Read -Path 'C:\I\Do\Not\Exist' -PassThru -ErrorAction SilentlyContinue $result | Should -BeNullOrEmpty - $Global:Error.Count | Should -BeGreaterThan 0 - $Global:Error[0] | Should -Match 'Cannot find path' + $Global:Error | Should -HaveCount 1 + $Global:Error | Should -Match 'path does not exist' } It 'when clearing a permission that already exists on a file' { @@ -495,141 +480,6 @@ Describe 'Grant-CPermission' { $Global:Error | Should -BeNullOrEmpty } - $skip = (Test-Path -Path 'env:WHS_CI') -and $env:WHS_CI -eq 'True' -and $PSVersionTable['PSVersion'].Major -eq 7 - $testCases = @('LocalMachine', 'CurrentUser') - It 'when setting permissions on a private key in the <_> location' -TestCases $testCases -Skip:$skip { - $location = $_ - $cert = Install-CCertificate -Path $script:privateKeyPath -StoreLocation $location -StoreName My -PassThru - try - { - $certPath = Join-Path -Path ('cert:\{0}\My' -f $location) -ChildPath $cert.Thumbprint - - # CryptoKey does not exist in .NET standard/core so we will have to use FileSystem instead - if ((Invoke-TestCCryptoKeyAvailable)) - { - $expectedProviderName = 'CryptoKey' - $readPermission = 'GenericRead' - $readRights = [CryptoKeyRights]::GenericRead,[CryptoKeyRights]::Synchronize - $readRightsForDeny = [CryptoKeyRights]::GenericRead - $writePermission = 'GenericWrite' - # $expectedPerm = 'GenericAll' - $writeRights = [CryptoKeyRights]::GenericAll,[CryptoKeyRights]::GenericRead,[CryptoKeyRights]::Synchronize - } - else - { - $expectedProviderName = 'FileSystem' - $readPermission = 'Read' - $readRights = [FileSystemRights]::Read - $readRightsForDeny = [FileSystemRights]::Read - $writePermission = 'Write' - $writeRights = [FileSystemRights]::Write - } - - $cert | Should -Not -BeNullOrEmpty - - # Context 'adds permissions' { - Invoke-GrantPermissions -Path $certPath ` - -Identity $script:user ` - -Permission $writePermission ` - -ProviderName $expectedProviderName ` - -ExpectedPermission $writeRights - ThenPermission -On $certPath -For $script:user -Is $writeRights - - # Context 'changes permissions' { - Invoke-GrantPermissions -Path $certPath ` - -Identity $script:user ` - -Permission $readPermission ` - -ProviderName $expectedProviderName ` - -ExpectedPermission $readRights - ThenPermission -On $certPath -For $script:user -Is $readRights - - # Context 'clearing others'' permissions' { - Invoke-GrantPermissions -Path $certPath ` - -Identity $script:user2 ` - -Permission $readPermission ` - -ProviderName $expectedProviderName ` - -ExpectedPermission $readRights ` - -Clear - ThenPermission -On $certPath -For $script:user2 -Is $readRights - Test-CPermission -Path $certPath -Identity $script:user -Permission $readPermission | Should -BeFalse - - # Context 'clearing others'' permissions when permissions getting set haven''t changed' { - Invoke-GrantPermissions -Path $certPath ` - -Identity $script:user ` - -Permission $readPermission ` - -ProviderName $expectedProviderName ` - -ExpectedPermission $readRights - ThenPermission -On $certPath -For $script:user -Is $readRights - Invoke-GrantPermissions -Path $certPath ` - -Identity $script:user2 ` - -Permission $readPermission ` - -ProviderName $expectedProviderName ` - -ExpectedPermission $readRights ` - -Clear - ThenPermission -On $certPath -For $script:user2 -Is $readRights - Test-CPermission -Path $certPath -Identity $script:user -Permission $readPermission | Should -BeFalse - - # Context 'running with -WhatIf switch' { - Grant-CPermission -Path $certPath -Identity $script:user2 -Permission $writePermission -WhatIf - Test-CPermission -Path $certPath -Identity $script:user2 -Permission $readPermission -Strict | - Should -BeTrue - Test-CPermission -Path $certPath -Identity $script:user2 -Permission $writePermission -Strict | - Should -BeFalse - - # Context 'creating a deny rule' { - Invoke-GrantPermissions -Path $certPath ` - -Identity $script:user ` - -Permission $readPermission ` - -Type 'Deny' ` - -ProviderName $expectedProviderName ` - -ExpectedPermission $readRightsForDeny - ThenPermission -On $certPath -For $script:user -Is $readRightsForDeny -OfType Deny - - # CryptoKey does not exist in .NET standard/core - if( (Invoke-TestCCryptoKeyAvailable) ) - { - Mock -CommandName 'Set-CCryptoKeySecurity' -Verifiable -ModuleName 'Carbon.Permissions' - - # Context 'permissions exist' { - # Now, check that permissions don't get re-applied. - Grant-CPermission -Path $certPath -Identity $script:user2 -Permission $readPermission - Should -Invoke 'Set-CCryptoKeySecurity' -ModuleName 'Carbon.Permissions' -Times 0 - - # Context 'permissions exist but forcing the change' { - Grant-CPermission -Path $certPath -Identity $script:user2 -Permission $readPermission -Force - Should -Invoke 'Set-CCryptoKeySecurity' -ModuleName 'Carbon.Permissions' -Times 1 -Exactly - } - } - finally - { - Uninstall-CCertificate -Thumbprint $cert.Thumbprint -StoreLocation $location -StoreName My - } - } - - It 'grants permissions to cng key' { - $certPath = Join-Path -Path $PSScriptRoot -ChildPath 'Certificates\CarbonRsaCng.pfx' -Resolve - $cert = Install-CCertificate -Path $certPath -StoreLocation CurrentUser -StoreName My -PassThru - $expectedRights = [FileSystemRights]::Write - - try - { - $certPath = Join-Path -Path 'cert:\CurrentUser\My' -ChildPath $cert.Thumbprint - - $cert | Should -Not -BeNullOrEmpty - - Invoke-GrantPermissions -Path $certPath ` - -Identity $script:user ` - -Permission 'GenericWrite' ` - -ProviderName 'FileSystem' ` - -ExpectedPermission $expectedRights - ThenPermission -On $certPath -For $script:user -Is $expectedRights - } - finally - { - Uninstall-CCertificate -Thumbprint $cert.Thumbprint -StoreLocation CurrentUser -StoreName My - } - } - It 'when setting Deny rule on file system' { $filePath = New-TestFile Invoke-GrantPermissions -Identity $script:user -Permissions 'Write' -Path $filePath -Type 'Deny' diff --git a/Tests/Revoke-CPermission.Tests.ps1 b/Tests/Revoke-CPermission.Tests.ps1 index 5f42922..4eebb9e 100644 --- a/Tests/Revoke-CPermission.Tests.ps1 +++ b/Tests/Revoke-CPermission.Tests.ps1 @@ -7,17 +7,9 @@ BeforeAll { & (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Test.ps1' -Resolve) - $psModulesPath = Join-Path -Path $PSScriptRoot -ChildPath '..\PSModules' -Resolve - Import-Module -Name (Join-Path -Path $psModulesPath -ChildPath 'Carbon.Cryptography' -Resolve) ` - -Function ('Install-CCertificate', 'Uninstall-CCertificate') ` - -Global ` - -Verbose:$false - $script:testDirPath = '' $script:testNum = 0 $script:username = 'CarbonGrantPerms' - $containerPath = $null - $privateKeyPath = Join-Path -Path $PSScriptRoot -ChildPath 'Certificates\CarbonTestPrivateKey.pfx' -Resolve } Describe 'Revoke-CPermission' { @@ -55,7 +47,8 @@ Describe 'Revoke-CPermission' { $result = Revoke-CPermission -Path $script:testDirPath -Identity $_.IdentityReference $Global:Error.Count | Should -Be 0 $result | Should -BeNullOrEmpty - (Test-CPermission -Identity $_.IdentityReference -Path $script:testDirPath -Inherited -Permission $_.FileSystemRights) | Should -BeTrue + (Test-CPermission -Identity $_.IdentityReference -Path $script:testDirPath -Inherited -Permission $_.FileSystemRights) | + Should -BeTrue } } @@ -101,72 +94,4 @@ Describe 'Revoke-CPermission' { Remove-Item $regKey } } - - It 'should revoke local machine private key permissions' { - $cert = Install-CCertificate -Path $privateKeyPath -StoreLocation LocalMachine -StoreName My -PassThru - try - { - $certPath = Join-Path -Path 'cert:\LocalMachine\My' -ChildPath $cert.Thumbprint - Grant-CPermission -Path $certPath -Identity $script:username -Permission 'FullControl' - (Get-CPermission -Path $certPath -Identity $script:username) | Should -Not -BeNullOrEmpty - Revoke-CPermission -Path $certPath -Identity $script:username - $Global:Error.Count | Should -Be 0 - (Get-CPermission -Path $certPath -Identity $script:username) | Should -BeNullOrEmpty - } - finally - { - Uninstall-CCertificate -Thumbprint $cert.Thumbprint -StoreLocation LocalMachine -StoreName My - } - } - - It 'should revoke current user private key permissions' { - $cert = Install-CCertificate -Path $privateKeyPath -StoreLocation CurrentUser -StoreName My -PassThru - try - { - $certPath = Join-Path -Path 'cert:\CurrentUser\My' -ChildPath $cert.Thumbprint - Grant-CPermission -Path $certPath -Identity $script:username -Permission 'FullControl' -WhatIf - $Global:Error.Count | Should -Be 0 - (Get-CPermission -Path $certPath -Identity $script:username) | Should -BeNullOrEmpty - } - finally - { - Uninstall-CCertificate -Thumbprint $cert.Thumbprint -StoreLocation CurrentUser -StoreName My - } - } - - It 'should support what if when revoking private key permissions' { - $cert = Install-CCertificate -Path $privateKeyPath -StoreLocation LocalMachine -StoreName My -PassThru - try - { - $certPath = Join-Path -Path 'cert:\LocalMachine\My' -ChildPath $cert.Thumbprint - Grant-CPermission -Path $certPath -Identity $script:username -Permission 'FullControl' - (Get-CPermission -Path $certPath -Identity $script:username) | Should -Not -BeNullOrEmpty - Revoke-CPermission -Path $certPath -Identity $script:username -WhatIf - $Global:Error.Count | Should -Be 0 - (Get-CPermission -Path $certPath -Identity $script:username) | Should -Not -BeNullOrEmpty - } - finally - { - Uninstall-CCertificate -Thumbprint $cert.Thumbprint -StoreLocation LocalMachine -StoreName My - } - } - - It 'revokes permission on cng certificate' { - $cngCertPath = Join-Path -Path $PSScriptRoot -ChildPath 'Certificates\CarbonRsaCng.pfx' -Resolve - $cert = Install-CCertificate -Path $cngCertPath -StoreLocation LocalMachine -StoreName My -PassThru - try - { - $certPath = Join-Path -Path 'cert:\LocalMachine\My' -ChildPath $cert.Thumbprint - Grant-CPermission -Path $certPath -Identity $script:username -Permission 'FullControl' - Get-CPermission -Path $certPath -Identity $script:username | Should -Not -BeNullOrEmpty - Revoke-CPermission -Path $certPath -Identity $script:username - $Global:Error.Count | Should -Be 0 - Get-CPermission -Path $certPath -Identity $script:username | Should -BeNullOrEmpty - } - finally - { - Uninstall-CCertificate -Thumbprint $cert.Thumbprint -StoreLocation LocalMachine -StoreName My - } - } - } diff --git a/Tests/Test-CPermission.Tests.ps1 b/Tests/Test-CPermission.Tests.ps1 index e9ff508..9366143 100644 --- a/Tests/Test-CPermission.Tests.ps1 +++ b/Tests/Test-CPermission.Tests.ps1 @@ -10,10 +10,6 @@ BeforeAll { & (Join-Path -Path $PSScriptRoot -ChildPath 'Initialize-Test.ps1' -Resolve) $psModulesPath = Join-Path -Path $PSScriptRoot -ChildPath '..\PSModules' -Resolve - Import-Module -Name (Join-Path -Path $psModulesPath -ChildPath 'Carbon.Cryptography' -Resolve) ` - -Function ('Install-CCertificate', 'Uninstall-CCertificate') ` - -Global ` - -Verbose:$false Import-Module -Name (Join-Path -Path $psModulesPath -ChildPath 'Carbon.Registry' -Resolve) ` -Function @('Install-CRegistryKey') ` -Global ` @@ -23,8 +19,6 @@ BeforeAll { $script:tempDir = Join-Path -Path $env:TEMP -ChildPath "Carbon-Test-CPermission-$([IO.Path]::GetRandomFileName())" New-Item (Join-Path -path $script:tempDir -ChildPath 'File') -ItemType File -Force - $script:privateKeyPath = Join-Path -Path $PSScriptRoot -ChildPath 'Certificates\CarbonTestPrivateKey.pfx' -Resolve - $script:dirPath = Join-Path -Path $script:tempDir -ChildPath 'Directory' $script:filePath = Join-Path -Path $script:dirPath -ChildPath 'File' New-Item -Path $script:filePath -ItemType File -Force -ErrorAction Ignore @@ -47,7 +41,6 @@ BeforeAll { Identity = $script:identity; } - $script:testFilePermArgs = @{ Path = $script:filePath; Identity = $script:identity; @@ -67,7 +60,8 @@ Describe 'Test-CPermission' { It 'should handle non existent path' { Test-CPermission -Path 'C:\I\Do\Not\Exist' -Identity $script:identity -Permission 'FullControl' -ErrorAction SilentlyContinue | Should -BeNullOrEmpty - $Global:Error | Should -HaveCount 2 + $Global:Error | Should -HaveCount 1 + $Global:Error | Should -Match 'path does not exist' } It 'should check ungranted permission on file system' { @@ -113,7 +107,7 @@ Describe 'Test-CPermission' { -WarningAction SilentlyContinue | Should -BeTrue $warning | Should -Not -BeNullOrEmpty - $warning[0] | Should -BeLike 'Can''t test "applies to" flags on a leaf.*' + $warning[0] | Should -Match 'test "applies to" flags on path.*because it is a file' } It 'should check ungranted permission on registry' { @@ -140,46 +134,4 @@ Describe 'Test-CPermission' { Test-CPermission @testDirPermArgs -Permission 'ReadAndExecute' -ApplyTo LeavesOnly -OnlyApplyToChildren | Should -BeFalse } - - It 'should check permission on private key' { - $cert = Install-CCertificate -Path $script:privateKeyPath -StoreLocation LocalMachine -StoreName My -PassThru - try - { - $certPath = Join-Path -Path 'cert:\LocalMachine\My' -ChildPath $cert.Thumbprint - # PowerShell (Core) uses file system rights on private keys, not crypto key rights. - $allPerm = 'FullControl' - $readPerm = 'Read' - if ([Type]::GetType('System.Security.AccessControl.CryptoKeyAccessRule')) - { - $allPerm = 'GenericAll' - $readPerm = 'GenericRead' - } - Grant-CPermission -Path $certPath -Identity $script:identity -Permission $allPerm - Test-CPermission -Path $certPath -Identity $script:identity -Permission $readPerm | Should -BeTrue - Test-CPermission -Path $certPath -Identity $script:identity -Permission $readPerm -Strict | - Should -BeFalse - Test-CPermission -Path $certPath -Identity $script:identity -Permission $allPerm, $readPerm -Strict | - Should -BeTrue - } - finally - { - Uninstall-CCertificate -Thumbprint $cert.Thumbprint -StoreLocation LocalMachine -StoreName My - } - } - - $script:usesFileSystemPermsOnPrivateKeys = - $null -eq [Type]::GetType('System.Security.AccessControl.CryptoKeyAccessRule') - It 'should check permission on public key' -Skip:$script:usesFileSystemPermsOnPrivateKeys { - $cert = - Get-Item -Path 'Cert:\*\*' | - Where-Object 'Name' -NE 'UserDS' | # This store causes problems on PowerShell 7. - Get-ChildItem | - Where-Object { -not $_.HasPrivateKey } | - Select-Object -First 1 - $cert | Should -Not -BeNullOrEmpty - $certPath = Join-Path -Path 'cert:\' -ChildPath (Split-Path -NoQualifier -Path $cert.PSPath) - Get-CPermission -path $certPath -Identity $script:identity | Out-String | Write-Host - Test-CPermission -Path $certPath -Identity $script:identity -Permission 'FullControl' | Should -BeTrue - Test-CPermission -Path $certPath -Identity $script:identity -Permission 'FullControl' -Strict | Should -BeTrue - } } diff --git a/prism.json b/prism.json index b98dccd..dbf0192 100644 --- a/prism.json +++ b/prism.json @@ -4,10 +4,6 @@ "Name": "Carbon", "Version": "2.*" }, - { - "Name": "Carbon.Cryptography", - "Version": "3.*" - }, { "Name": "Carbon.Registry", "Version": "1.*" diff --git a/prism.lock.json b/prism.lock.json index be0df6c..a6ff0fb 100644 --- a/prism.lock.json +++ b/prism.lock.json @@ -5,11 +5,6 @@ "version": "2.15.1", "repositorySourceLocation": "https://www.powershellgallery.com/api/v2" }, - { - "name": "Carbon.Cryptography", - "version": "3.1.3", - "repositorySourceLocation": "https://www.powershellgallery.com/api/v2" - }, { "name": "Carbon.Registry", "version": "1.0.0",