Make 'AllSigned' execution policy require modules under $PSHome to be signed (#5511)

This commit is contained in:
Travis Plunk
2017-11-28 17:34:37 -08:00
committed by Dongbo Wang
parent 9bedef0c62
commit f67844e4cd
2 changed files with 190 additions and 66 deletions
@@ -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
}
}
}
}