mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Make 'AllSigned' execution policy require modules under $PSHome to be signed (#5511)
This commit is contained in:
committed by
Dongbo Wang
parent
9bedef0c62
commit
f67844e4cd
@@ -48,7 +48,7 @@ namespace Microsoft.PowerShell
|
||||
/// Unrestricted - No files must be signed. If a file originates from the
|
||||
/// internet, Monad provides a warning prompt to alert the user. To
|
||||
/// suppress this warning message, right-click on the file in File Explorer,
|
||||
/// select "Properties," and then "Unblock."
|
||||
/// select "Properties," and then "Unblock." Requires Shell.
|
||||
/// Bypass - No files must be signed, and internet origin is not verified
|
||||
///
|
||||
/// </summary>
|
||||
@@ -136,10 +136,6 @@ namespace Microsoft.PowerShell
|
||||
if (!IsSupportedExtension(fi.Extension))
|
||||
return true;
|
||||
|
||||
// Product binaries are always trusted
|
||||
if (SecuritySupport.IsProductBinary(path))
|
||||
return true;
|
||||
|
||||
// Get the execution policy
|
||||
_executionPolicy = SecuritySupport.GetExecutionPolicy(_shellId);
|
||||
|
||||
@@ -189,6 +185,11 @@ namespace Microsoft.PowerShell
|
||||
#endif
|
||||
if (_executionPolicy == ExecutionPolicy.Unrestricted)
|
||||
{
|
||||
// Product binaries are always trusted
|
||||
// This avoids signature and security zone checks
|
||||
if (SecuritySupport.IsProductBinary(path))
|
||||
return true;
|
||||
|
||||
// We need to give the "Remote File" warning
|
||||
// if the file originated from the internet
|
||||
if (!IsLocalFile(fi.FullName))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
Import-Module HelpersCommon
|
||||
#
|
||||
# These are general tests that verify non-Windows behavior
|
||||
#
|
||||
@@ -95,6 +96,8 @@ try {
|
||||
$LocalSignatureCorruptedScript = Join-Path -Path $remoteTestDirectory -ChildPath LocalSignatureCorruptedScript.ps1
|
||||
$LocalSignedScript = Join-Path -Path $remoteTestDirectory -ChildPath LocalSignedScript.ps1
|
||||
$LocalUnsignedScript = Join-Path -Path $remoteTestDirectory -ChildPath LocalUnsignedScript.ps1
|
||||
$PSHomeUnsignedModule = Join-Path -Path $PSHome -ChildPath 'Modules' -AdditionalChildPath 'LocalUnsignedModule', 'LocalUnsignedModule.psm1'
|
||||
$PSHomeUntrustedModule = Join-Path -Path $PSHome -ChildPath 'Modules' -AdditionalChildPath 'LocalUntrustedModule', 'LocalUntrustedModule.psm1'
|
||||
$TrustedSignatureCorruptedScript = Join-Path -Path $remoteTestDirectory -ChildPath TrustedSignatureCorruptedScript.ps1
|
||||
$TrustedSignedScript = Join-Path -Path $remoteTestDirectory -ChildPath TrustedSignedScript.ps1
|
||||
$TrustedUnsignedScript = Join-Path -Path $remoteTestDirectory -ChildPath TrustedUnsignedScript.ps1
|
||||
@@ -169,6 +172,18 @@ try {
|
||||
AddSignature = $false
|
||||
Corrupted = $false
|
||||
}
|
||||
@{
|
||||
FilePath = $PSHomeUnsignedModule
|
||||
FileType = $fileType.Local
|
||||
AddSignature = $false
|
||||
Corrupted = $false
|
||||
}
|
||||
@{
|
||||
FilePath = $PSHomeUntrustedModule
|
||||
FileType = $fileType.Untrusted
|
||||
AddSignature = $false
|
||||
Corrupted = $false
|
||||
}
|
||||
@{
|
||||
FilePath = $TrustedSignatureCorruptedScript
|
||||
FileType = $fileType.Trusted
|
||||
@@ -230,9 +245,11 @@ try {
|
||||
function createTestFile
|
||||
{
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[string]
|
||||
$FilePath,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[int]
|
||||
$FileType,
|
||||
|
||||
@@ -243,7 +260,14 @@ try {
|
||||
$Corrupted
|
||||
)
|
||||
|
||||
$null = New-Item -Path $filePath -ItemType File
|
||||
$folder = Split-Path -Path $FilePath
|
||||
# create folder if it doesn't already exist
|
||||
if(!(Test-Path $folder))
|
||||
{
|
||||
$null = New-Item -Path $folder -ItemType Directory
|
||||
}
|
||||
|
||||
$null = New-Item -Path $filePath -ItemType File -Force
|
||||
|
||||
$content = "`"Hello`"" + "`r`n"
|
||||
if($AddSignature)
|
||||
@@ -460,6 +484,34 @@ ZoneId=$FileType
|
||||
#Get Execution Policy
|
||||
$originalExecPolicy = Get-ExecutionPolicy
|
||||
$originalExecutionPolicy = $originalExecPolicy
|
||||
|
||||
$archiveSigned = $false
|
||||
$archivePath = Get-Module -ListAvailable Microsoft.PowerShell.Archive -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Path
|
||||
if($archivePath)
|
||||
{
|
||||
$archiveFolder = Split-Path -Path $archivePath
|
||||
|
||||
# get all the certs used to sign the module
|
||||
$script:archiveAllCert = Get-ChildItem -File -Path (Join-Path -Path $archiveFolder -ChildPath '*') -Recurse |
|
||||
Get-AuthenticodeSignature
|
||||
|
||||
# filter only to valid signatures
|
||||
$script:archiveCert = $script:archiveAllCert |
|
||||
Where-Object { $_.status -eq 'Valid'} |
|
||||
Select-Object -Unique -ExpandProperty SignerCertificate
|
||||
|
||||
# if we have valid signatures, add them to trusted publishers so powershell will trust them.
|
||||
if($script:archiveCert)
|
||||
{
|
||||
$store = [System.Security.Cryptography.X509Certificates.X509Store]::new([System.Security.Cryptography.X509Certificates.StoreName]::TrustedPublisher,[System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser)
|
||||
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
|
||||
$archiveCert | ForEach-Object {
|
||||
$store.Add($_)
|
||||
}
|
||||
$store.Close()
|
||||
$archiveSigned = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AfterAll {
|
||||
@@ -472,6 +524,15 @@ ZoneId=$FileType
|
||||
}
|
||||
}
|
||||
|
||||
Context "Prereq: Validate that 'Microsoft.PowerShell.Archive' is signed" {
|
||||
It "'Microsoft.PowerShell.Archive' should have a signature" {
|
||||
$script:archiveAllCert | should not be null
|
||||
}
|
||||
It "'Microsoft.PowerShell.Archive' should have a valid signature" {
|
||||
$script:archiveCert | should not be null
|
||||
}
|
||||
}
|
||||
|
||||
Context "Validate that 'Restricted' execution policy works on OneCore powershell" {
|
||||
|
||||
BeforeAll {
|
||||
@@ -598,6 +659,36 @@ ZoneId=$FileType
|
||||
foreach($testScript in $testScripts) {
|
||||
Test-UnrestrictedExecutionPolicy $testScript $expected
|
||||
}
|
||||
|
||||
$error = "UnauthorizedAccess,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
||||
$testData = @(
|
||||
@{
|
||||
module = $PSHomeUntrustedModule
|
||||
error = $null
|
||||
}
|
||||
@{
|
||||
module = $PSHomeUnsignedModule
|
||||
error = $null
|
||||
}
|
||||
@{
|
||||
module = "Microsoft.PowerShell.Archive"
|
||||
error = $null
|
||||
}
|
||||
)
|
||||
|
||||
$TestTypePrefix = "Test 'Unrestricted' execution policy."
|
||||
It "$TestTypePrefix Importing <module> Module should throw '<error>'" -TestCases $testData {
|
||||
param([string]$module, [string]$error)
|
||||
$testScript = {Import-Module -Name $module -Force}
|
||||
if($error)
|
||||
{
|
||||
$testScript | ShouldBeErrorId $error
|
||||
}
|
||||
else
|
||||
{
|
||||
$testScript | Should Not throw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Context "Validate that 'ByPass' execution policy works on OneCore powershell" {
|
||||
@@ -813,102 +904,134 @@ ZoneId=$FileType
|
||||
}
|
||||
}
|
||||
|
||||
function Test-AllSignedExecutionPolicy {
|
||||
$TestTypePrefix = "Test 'AllSigned' execution policy."
|
||||
|
||||
param($testScript, $error)
|
||||
|
||||
$TestTypePrefix = "Test 'AllSigned' execution policy."
|
||||
|
||||
It "$TestTypePrefix Running $testScript script should return $error" {
|
||||
|
||||
$scriptName = $testScript
|
||||
|
||||
$exception = $null
|
||||
try
|
||||
{
|
||||
& $scriptName
|
||||
}
|
||||
catch
|
||||
{
|
||||
$exception = $_
|
||||
}
|
||||
$errorType = $null
|
||||
|
||||
if($null -ne $exception)
|
||||
{
|
||||
$errorType = $exception.exception.getType()
|
||||
}
|
||||
|
||||
$result = $errorType
|
||||
|
||||
$result | Should be $error
|
||||
}
|
||||
}
|
||||
$error = "System.Management.Automation.PSSecurityException"
|
||||
$error = "UnauthorizedAccess,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
||||
$testData = @(
|
||||
@{
|
||||
testScript = $LocalUnsignedScript
|
||||
expected = $null
|
||||
module = $PSHomeUntrustedModule
|
||||
error = $error
|
||||
}
|
||||
@{
|
||||
testScript = $LocalSignatureCorruptedScript
|
||||
expected = $null
|
||||
module = $PSHomeUnsignedModule
|
||||
error = $error
|
||||
}
|
||||
@{
|
||||
testScript = $MyComputerUnsignedScript
|
||||
expected = $null
|
||||
error = $error
|
||||
module = "Microsoft.PowerShell.Archive"
|
||||
error = $null
|
||||
}
|
||||
)
|
||||
|
||||
It "$TestTypePrefix Importing <module> Module should throw '<error>'" -TestCases $testData {
|
||||
param([string]$module, [string]$error)
|
||||
$testScript = {Import-Module -Name $module -Force}
|
||||
if($error)
|
||||
{
|
||||
$testScript | ShouldBeErrorId $error
|
||||
}
|
||||
else
|
||||
{
|
||||
$testScript | Should Not throw
|
||||
}
|
||||
}
|
||||
|
||||
$error = "UnauthorizedAccess"
|
||||
$pendingTestData = @(
|
||||
# The following files are not signed correctly when generated, so we will skip for now
|
||||
# filed https://github.com/PowerShell/PowerShell/issues/5559
|
||||
@{
|
||||
testScript = $MyComputerSignedScript
|
||||
error = $null
|
||||
}
|
||||
@{
|
||||
testScript = $MyComputerSignatureCorruptedScript
|
||||
expected = $null
|
||||
error = $error
|
||||
testScript = $UntrustedSignedScript
|
||||
error = $null
|
||||
}
|
||||
@{
|
||||
testScript = $TrustedUnsignedScript
|
||||
expected = $null
|
||||
error = $error
|
||||
testScript = $TrustedSignedScript
|
||||
error = $null
|
||||
}
|
||||
@{
|
||||
testScript = $TrustedSignatureCorruptedScript
|
||||
expected = $null
|
||||
error = $error
|
||||
testScript = $LocalSignedScript
|
||||
error = $null
|
||||
}
|
||||
@{
|
||||
testScript = $IntranetUnsignedScript
|
||||
expected = $null
|
||||
error = $error
|
||||
testScript = $IntranetSignedScript
|
||||
error = $null
|
||||
}
|
||||
@{
|
||||
testScript = $IntranetSignatureCorruptedScript
|
||||
expected = $null
|
||||
testScript = $InternetSignedScript
|
||||
error = $null
|
||||
}
|
||||
)
|
||||
It "$TestTypePrefix Running <testScript> Script should throw '<error>'" -TestCases $pendingTestData -Pending {}
|
||||
|
||||
$testData = @(
|
||||
@{
|
||||
testScript = $InternetSignatureCorruptedScript
|
||||
error = $error
|
||||
}
|
||||
@{
|
||||
testScript = $InternetUnsignedScript
|
||||
expected = $null
|
||||
error = $error
|
||||
}
|
||||
@{
|
||||
testScript = $InternetSignatureCorruptedScript
|
||||
expected = $null
|
||||
testScript = $IntranetSignatureCorruptedScript
|
||||
error = $error
|
||||
}
|
||||
@{
|
||||
testScript = $UntrustedUnsignedScript
|
||||
expected = $null
|
||||
testScript = $IntranetSignatureCorruptedScript
|
||||
error = $error
|
||||
}
|
||||
@{
|
||||
testScript = $IntranetUnsignedScript
|
||||
error = $error
|
||||
}
|
||||
@{
|
||||
testScript = $LocalSignatureCorruptedScript
|
||||
error = $error
|
||||
}
|
||||
@{
|
||||
testScript = $LocalUnsignedScript
|
||||
error = $error
|
||||
}
|
||||
@{
|
||||
testScript = $TrustedSignatureCorruptedScript
|
||||
error = $error
|
||||
}
|
||||
@{
|
||||
testScript = $TrustedUnsignedScript
|
||||
error = $error
|
||||
}
|
||||
@{
|
||||
testScript = $UntrustedSignatureCorruptedScript
|
||||
expected = $null
|
||||
error = $error
|
||||
}
|
||||
@{
|
||||
testScript = $UntrustedUnsignedScript
|
||||
error = $error
|
||||
}
|
||||
@{
|
||||
testScript = $MyComputerSignatureCorruptedScript
|
||||
error = $error
|
||||
}
|
||||
@{
|
||||
testScript = $MyComputerUnsignedScript
|
||||
error = $error
|
||||
}
|
||||
|
||||
)
|
||||
foreach($testScript in $testScripts) {
|
||||
Test-AllSignedExecutionPolicy $testScript $error
|
||||
It "$TestTypePrefix Running <testScript> Script should throw '<error>'" -TestCases $testData {
|
||||
param([string]$testScript, [string]$error)
|
||||
$testScript | should exist
|
||||
if($error)
|
||||
{
|
||||
{& $testScript} | ShouldBeErrorId $error
|
||||
}
|
||||
else
|
||||
{
|
||||
{& $testScript} | Should Not throw
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user