diff --git a/src/CodeSigning/CodeSigning.Test/CodeSigning.Test.csproj b/src/CodeSigning/CodeSigning.Test/CodeSigning.Test.csproj new file mode 100644 index 000000000000..a56992acb9a7 --- /dev/null +++ b/src/CodeSigning/CodeSigning.Test/CodeSigning.Test.csproj @@ -0,0 +1,35 @@ + + + + CodeSigning + + + + + + $(LegacyAssemblyPrefix)$(PsModuleName)$(AzTestAssemblySuffix) + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CodeSigning/CodeSigning.Test/Resource.resx b/src/CodeSigning/CodeSigning.Test/Resource.resx new file mode 100644 index 000000000000..2f96abecd49c --- /dev/null +++ b/src/CodeSigning/CodeSigning.Test/Resource.resx @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + \ No newline at end of file diff --git a/src/CodeSigning/CodeSigning.Test/ScenarioTests/CodeSigningTestRunner.cs b/src/CodeSigning/CodeSigning.Test/ScenarioTests/CodeSigningTestRunner.cs new file mode 100644 index 000000000000..269fded31e11 --- /dev/null +++ b/src/CodeSigning/CodeSigning.Test/ScenarioTests/CodeSigningTestRunner.cs @@ -0,0 +1,41 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using Microsoft.Azure.Commands.TestFx; +using Xunit.Abstractions; + +namespace Microsoft.Azure.Commands.CodeSigning.Test.ScenarioTests +{ + public class CodeSigningTestRunner + { + protected readonly ITestRunner TestRunner; + + protected CodeSigningTestRunner(ITestOutputHelper output) + { + TestRunner = TestManager.CreateInstance(output) + .WithNewPsScriptFilename($"{GetType().Name}.ps1") + .WithProjectSubfolderForTests("ScenarioTests") + .WithCommonPsScripts(new[] + { + @"../AzureRM.Resources.ps1" + }) + .WithNewRmModules(helper => new[] + { + helper.RMProfileModule, + helper.GetRMModulePath("Az.CodeSigning.psd1"), + }) + .Build(); + } + } +} diff --git a/src/CodeSigning/CodeSigning.Test/ScenarioTests/CodeSigningTests.cs b/src/CodeSigning/CodeSigning.Test/ScenarioTests/CodeSigningTests.cs new file mode 100644 index 000000000000..044823a79172 --- /dev/null +++ b/src/CodeSigning/CodeSigning.Test/ScenarioTests/CodeSigningTests.cs @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using Microsoft.WindowsAzure.Commands.ScenarioTest; +using Xunit; + +namespace Microsoft.Azure.Commands.CodeSigning.Test.ScenarioTests +{ + public class CodeSigningTests: CodeSigningTestRunner + { + public CodeSigningTests(Xunit.Abstractions.ITestOutputHelper output) : base(output) + { + } + + [Fact] + [Trait(Category.AcceptanceType, Category.LiveOnly)] + public void TestCodeSigningEku() + { + TestRunner.RunTestScript("Test-CodeSigningEku"); + } + + [Fact] + [Trait(Category.AcceptanceType, Category.LiveOnly)] + public void TestGetSigningRootCertificate() + { + TestRunner.RunTestScript("Test-GetCodeSigningRootCert"); + } + } +} diff --git a/src/CodeSigning/CodeSigning.Test/ScenarioTests/CodeSigningTests.ps1 b/src/CodeSigning/CodeSigning.Test/ScenarioTests/CodeSigningTests.ps1 new file mode 100644 index 000000000000..5d4ed128fa94 --- /dev/null +++ b/src/CodeSigning/CodeSigning.Test/ScenarioTests/CodeSigningTests.ps1 @@ -0,0 +1,56 @@ +# ---------------------------------------------------------------------------------- +# +# Copyright Microsoft Corporation +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------------- + +<# +.SYNOPSIS +Test codesigning command to get extended key usage from the certificate profile +#> +function Test-CodeSigningEku { + + $accountName = "acs-test-account" + $profileName = "acs-test-account-ci" + $endPointUrl = "https://scus.codesigning.azure.net/" + $expectedEku = "1.3.6.1.4.1.311.97.1.3.1.29433.35007.34545.16815.37291.11644.53265.56135,1.3.6.1.4.1.311.97.1.4.1.29433.35007.34545.16815.37291.11644.53265.56135" + + try { + # Test Get CodeSigning Eku + $eku = Get-AzCodeSigningCustomerEku -AccountName $accountName -ProfileName $profileName -EndpointUrl $endPointUrl + Assert-AreEqual $eku $expectedEku + } + + finally { + + } +} + +<# +.SYNOPSIS +Test codesigning command to get the root certificate from the certificate profile +#> +function Test-GetCodeSigningRootCert { + $accountName = "acs-test-account" + $profileName = "acs-test-account-ci" + $endPointUrl = "https://scus.codesigning.azure.net/" + $destination = "C:\temp" + + try { + # Test Get CodeSigning Root Cert + $cert = Get-AzCodeSigningRootCert -AccountName $accountName -ProfileName $profileName -EndpointUrl $endPointUrl -Destination $destination + Assert-NotNullOrEmpty $cert + } + + finally { + + } +} \ No newline at end of file diff --git a/src/CodeSigning/CodeSigning.Test/ScenarioTests/Common.ps1 b/src/CodeSigning/CodeSigning.Test/ScenarioTests/Common.ps1 new file mode 100644 index 000000000000..e223774efd14 --- /dev/null +++ b/src/CodeSigning/CodeSigning.Test/ScenarioTests/Common.ps1 @@ -0,0 +1,130 @@ +# ---------------------------------------------------------------------------------- +# +# Copyright Microsoft Corporation +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------------- + +<# +.SYNOPSIS +Get Subscription ID +#> +function Get-SubscriptionID +{ + $context = Get-AzContext + return $context.Subscription.SubscriptionId +} + +<# +.SYNOPSIS +Get ResourceGroup name +#> +function Get-ResourceGroupName +{ + return "RGName-" + (getAssetName) +} + +<# +.SYNOPSIS +Gets valid resource group name +#> +function Get-ResourceGroupName +{ + return getAssetName +} + +<# +.SYNOPSIS +Create new ResourceGroup +#> +function New-ResourceGroup($ResourceGroupName, $Location) +{ + Write-Debug "Creating resource group name $ResourceGroupName in location $Location" + New-AzResourceGroup -Name $ResourceGroupName -Location $Location -Force +} + +<# +.SYNOPSIS +Remove ResourceGroup +#> +function Remove-ResourceGroup($ResourceGroupName) +{ + Write-Debug "Deleting resource group name $ResourceGroupName" + Remove-AzResourceGroup -Name $ResourceGroupName -Force +} + +<# +.SYNOPSIS +Gets valid resource name +#> +function Get-VaultName +{ + return getAssetName +} + +<# +.SYNOPSIS +Gets test mode - 'Record' or 'Playback' +#> +function Get-KeyVaultTestMode { + try { + $testMode = [Microsoft.Azure.Test.HttpRecorder.HttpMockServer]::Mode; + $testMode = $testMode.ToString(); + } catch { + if ($PSItem.Exception.Message -like '*Unable to find type*') { + $testMode = 'Record'; + } else { + throw; + } + } + + return $testMode +} + +<# +.SYNOPSIS +Gets the default location for a provider +#> +function Get-ProviderLocation($provider) +{ + if ((Get-KeyVaultTestMode) -ne 'Playback') + { + $namespace = $provider.Split("/")[0] + if($provider.Contains("/")) + { + $type = $provider.Substring($namespace.Length + 1) + $location = Get-AzResourceProvider -ProviderNamespace $namespace | where {$_.ResourceTypes[0].ResourceTypeName -eq $type} + + if ($location -eq $null) + { + return "East US" + } + else + { + return $location.Locations[0] + } + } + + return "East US" + } + + return "East US" +} + +<# +.SYNOPSIS +Cleans the created resource groups +#> +function Clean-ResourceGroup($rgname) +{ + if ((Get-KeyVaultTestMode) -ne 'Playback') { + Remove-AzResourceGroup -Name $rgname -Force + } +} diff --git a/src/CodeSigning/CodeSigning.sln b/src/CodeSigning/CodeSigning.sln new file mode 100644 index 000000000000..611a7c395216 --- /dev/null +++ b/src/CodeSigning/CodeSigning.sln @@ -0,0 +1,66 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32901.215 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeSigning", "CodeSigning\CodeSigning.csproj", "{9FFC40CC-A341-4D0C-A25D-DC6B78EF6C94}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Accounts", "..\Accounts\Accounts\Accounts.csproj", "{142D7B0B-388A-4CEB-A228-7F6D423C5C2E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Authenticators", "..\Accounts\Authenticators\Authenticators.csproj", "{6BD6B80A-06AF-4B5B-9230-69CCFC6C8D64}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Authentication", "..\Accounts\Authentication\Authentication.csproj", "{FF81DC73-B8EC-4082-8841-4FBF2B16E7CE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Authentication.ResourceManager", "..\Accounts\Authentication.ResourceManager\Authentication.ResourceManager.csproj", "{3E016018-D65D-4336-9F64-17DA97783AD0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestFx", "..\..\tools\TestFx\TestFx.csproj", "{BC80A1D0-FFA4-43D9-AA74-799F5CB54B58}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeSigning.Test", "CodeSigning.Test\CodeSigning.Test.csproj", "{1F35D6C0-5310-43F9-A320-193A2C1AEEC8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssemblyLoading", "..\Accounts\AssemblyLoading\AssemblyLoading.csproj", "{1F6E85D8-6920-4F62-B378-F4948BF8DF2D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9FFC40CC-A341-4D0C-A25D-DC6B78EF6C94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FFC40CC-A341-4D0C-A25D-DC6B78EF6C94}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FFC40CC-A341-4D0C-A25D-DC6B78EF6C94}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FFC40CC-A341-4D0C-A25D-DC6B78EF6C94}.Release|Any CPU.Build.0 = Release|Any CPU + {142D7B0B-388A-4CEB-A228-7F6D423C5C2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {142D7B0B-388A-4CEB-A228-7F6D423C5C2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {142D7B0B-388A-4CEB-A228-7F6D423C5C2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {142D7B0B-388A-4CEB-A228-7F6D423C5C2E}.Release|Any CPU.Build.0 = Release|Any CPU + {6BD6B80A-06AF-4B5B-9230-69CCFC6C8D64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6BD6B80A-06AF-4B5B-9230-69CCFC6C8D64}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BD6B80A-06AF-4B5B-9230-69CCFC6C8D64}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6BD6B80A-06AF-4B5B-9230-69CCFC6C8D64}.Release|Any CPU.Build.0 = Release|Any CPU + {FF81DC73-B8EC-4082-8841-4FBF2B16E7CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF81DC73-B8EC-4082-8841-4FBF2B16E7CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF81DC73-B8EC-4082-8841-4FBF2B16E7CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF81DC73-B8EC-4082-8841-4FBF2B16E7CE}.Release|Any CPU.Build.0 = Release|Any CPU + {3E016018-D65D-4336-9F64-17DA97783AD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3E016018-D65D-4336-9F64-17DA97783AD0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3E016018-D65D-4336-9F64-17DA97783AD0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3E016018-D65D-4336-9F64-17DA97783AD0}.Release|Any CPU.Build.0 = Release|Any CPU + {BC80A1D0-FFA4-43D9-AA74-799F5CB54B58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC80A1D0-FFA4-43D9-AA74-799F5CB54B58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC80A1D0-FFA4-43D9-AA74-799F5CB54B58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC80A1D0-FFA4-43D9-AA74-799F5CB54B58}.Release|Any CPU.Build.0 = Release|Any CPU + {1F35D6C0-5310-43F9-A320-193A2C1AEEC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F35D6C0-5310-43F9-A320-193A2C1AEEC8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F35D6C0-5310-43F9-A320-193A2C1AEEC8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F35D6C0-5310-43F9-A320-193A2C1AEEC8}.Release|Any CPU.Build.0 = Release|Any CPU + {1F6E85D8-6920-4F62-B378-F4948BF8DF2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F6E85D8-6920-4F62-B378-F4948BF8DF2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F6E85D8-6920-4F62-B378-F4948BF8DF2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F6E85D8-6920-4F62-B378-F4948BF8DF2D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5E85B4CC-D1A9-466B-98AC-E0AD0C5AE585} + EndGlobalSection +EndGlobal diff --git a/src/CodeSigning/CodeSigning/Az.CodeSigning.psd1 b/src/CodeSigning/CodeSigning/Az.CodeSigning.psd1 new file mode 100644 index 000000000000..9088181dbde4 --- /dev/null +++ b/src/CodeSigning/CodeSigning/Az.CodeSigning.psd1 @@ -0,0 +1,136 @@ +# +# Module manifest for module 'Az.CodeSigning' +# +# Generated by: Microsoft Corporation +# +# Generated on: 3/6/2023 +# + +@{ + +# Script module or binary module file associated with this manifest. +# RootModule = '' + +# Version number of this module. +ModuleVersion = '0.1.0' + +# Supported PSEditions +CompatiblePSEditions = 'Core', 'Desktop' + +# ID used to uniquely identify this module +GUID = 'ea901aab-24ca-4004-a198-b9a37a4c8070' + +# Author of this module +Author = 'Microsoft Corporation' + +# Company or vendor of this module +CompanyName = 'Microsoft Corporation' + +# Copyright statement for this module +Copyright = 'Microsoft Corporation. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'Microsoft Azure PowerShell - Code Signing service cmdlets in Windows PowerShell and PowerShell Core. + +For more information on Key Vault, please visit the following: https://learn.microsoft.com/azure/key-vault/' + +# Minimum version of the PowerShell engine required by this module +PowerShellVersion = '5.1' + +# Name of the PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +DotNetFrameworkVersion = '4.7.2' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# ClrVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +RequiredModules = @(@{ModuleName = 'Az.Accounts'; ModuleVersion = '2.12.1'; }) + +# Assemblies that must be loaded prior to importing this module +RequiredAssemblies = 'Azure.CodeSigning.Client.CryptoProvider.dll', + 'Azure.CodeSigning.Client.CryptoProvider.Models.dll', + 'Azure.CodeSigning.Client.CryptoProvider.Utilities.dll', + 'Azure.CodeSigning.dll', + 'Polly.dll' + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +FormatsToProcess = 'CodeSigning.format.ps1xml' + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +NestedModules = @('Microsoft.Azure.PowerShell.Cmdlets.CodeSigning.dll') + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = @() + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = 'Get-AzCodeSigningCustomerEku', 'Get-AzCodeSigningRootCert', + 'Invoke-AzCodeSigningCIPolicySigning' + +# Variables to export from this module +# VariablesToExport = @() + +# DSC resources to export from this module +# DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +# FileList = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + Tags = 'Azure','CodeSigning' + + # A URL to the license for this module. + LicenseUri = 'https://aka.ms/azps-license' + + # A URL to the main website for this project. + ProjectUri = 'https://github.com/Azure/azure-powershell' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + ReleaseNotes = '* Initial release for public preview.' + + # Prerelease string of this module + # Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + + } # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} + diff --git a/src/CodeSigning/CodeSigning/ChangeLog.md b/src/CodeSigning/CodeSigning/ChangeLog.md new file mode 100644 index 000000000000..6feebbfb0504 --- /dev/null +++ b/src/CodeSigning/CodeSigning/ChangeLog.md @@ -0,0 +1,23 @@ + +## Upcoming Release + +* First preview release for module Az.CodeSigning + diff --git a/src/CodeSigning/CodeSigning/CodeSigning.csproj b/src/CodeSigning/CodeSigning/CodeSigning.csproj new file mode 100644 index 000000000000..c95dbd3fe934 --- /dev/null +++ b/src/CodeSigning/CodeSigning/CodeSigning.csproj @@ -0,0 +1,45 @@ + + + + CodeSigning + + + + + + true + Library + true + true + False + + + + 1701;1702;CS0214; + + + + 1701;1702;CS0214; + + + + + + + + + + + True + + + + + + True + + + + + + diff --git a/src/CodeSigning/CodeSigning/CodeSigning.format.ps1xml b/src/CodeSigning/CodeSigning/CodeSigning.format.ps1xml new file mode 100644 index 000000000000..d38037784adf --- /dev/null +++ b/src/CodeSigning/CodeSigning/CodeSigning.format.ps1xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/CodeSigning/CodeSigning/Commands/GetAzureCodeSigningCustomerEku.cs b/src/CodeSigning/CodeSigning/Commands/GetAzureCodeSigningCustomerEku.cs new file mode 100644 index 000000000000..ce4623cf7b8b --- /dev/null +++ b/src/CodeSigning/CodeSigning/Commands/GetAzureCodeSigningCustomerEku.cs @@ -0,0 +1,90 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using Microsoft.Azure.Commands.CodeSigning.Models; +using System; +using System.Management.Automation; + +namespace Microsoft.Azure.Commands.CodeSigning +{ + [Cmdlet("Get", ResourceManager.Common.AzureRMConstants.AzurePrefix + "CodeSigningCustomerEku", DefaultParameterSetName = ByAccountProfileNameParameterSet)] + [OutputType(typeof(string[]))] + public class GetAzureCodeSigningCustomerEku : CodeSigningCmdletBase + { + #region Parameter Set Names + + private const string ByAccountProfileNameParameterSet = "ByAccountProfileNameParameterSet"; + private const string ByMetadataFileParameterSet = "ByMetadataFileParameterSet"; + + #endregion + + #region Input Parameter Definitions + + /// + /// Account Profile name + /// + [Parameter(Mandatory = true, + Position = 0, + ParameterSetName = ByAccountProfileNameParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The account name of Azure CodeSigning.")] + [ValidateNotNullOrEmpty] + public string AccountName { get; set; } + + [Parameter(Mandatory = true, + Position = 1, + ParameterSetName = ByAccountProfileNameParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The certificate profile name of Azure CodeSigning account.")] + [ValidateNotNullOrEmpty()] + public string ProfileName { get; set; } + + [Parameter(Mandatory = true, + Position = 2, + ParameterSetName = ByAccountProfileNameParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The endpoint url used to submit request to Azure CodeSigning.")] + public string EndpointUrl { get; set; } + + + /// + /// Metadata File Path + /// + [Parameter(Mandatory = true, + Position = 0, + ParameterSetName = ByMetadataFileParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Metadata File path. Cmdlet constructs the FQDN of an account profile based on the Metadata File and currently selected environment.")] + [ValidateNotNullOrEmpty] + public string MetadataFilePath { get; set; } + + #endregion + + public override void ExecuteCmdlet() + { + string[] eku = Array.Empty(); + + if (!string.IsNullOrEmpty(AccountName)) + { + eku = CodeSigningServiceClient.GetCodeSigningEku(AccountName, ProfileName, EndpointUrl); + } + else if (!string.IsNullOrEmpty(MetadataFilePath)) + { + eku = CodeSigningServiceClient.GetCodeSigningEku(MetadataFilePath); + } + + WriteObject(eku); + } + } +} diff --git a/src/CodeSigning/CodeSigning/Commands/GetAzureCodeSigningRootCert.cs b/src/CodeSigning/CodeSigning/Commands/GetAzureCodeSigningRootCert.cs new file mode 100644 index 000000000000..5e5119d9c13c --- /dev/null +++ b/src/CodeSigning/CodeSigning/Commands/GetAzureCodeSigningRootCert.cs @@ -0,0 +1,124 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using Microsoft.Azure.Commands.CodeSigning.Models; +using System.IO; +using System.Management.Automation; +using System.Security.Cryptography.X509Certificates; + +namespace Microsoft.Azure.Commands.CodeSigning +{ + [Cmdlet("Get", ResourceManager.Common.AzureRMConstants.AzurePrefix + "CodeSigningRootCert", DefaultParameterSetName = ByAccountProfileNameParameterSet)] + [OutputType(typeof(string))] + public class GetAzureCodeSigningRootCert : CodeSigningCmdletBase + { + #region Parameter Set Names + + private const string ByAccountProfileNameParameterSet = "ByAccountProfileNameParameterSet"; + private const string ByMetadataFileParameterSet = "ByMetadataFileParameterSet"; + + #endregion + + #region Input Parameter Definitions + + /// + /// Account Profile name + /// + [Parameter(Mandatory = true, + Position = 0, + ParameterSetName = ByAccountProfileNameParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The account name of Azure CodeSigning.")] + [ValidateNotNullOrEmpty] + public string AccountName { get; set; } + + [Parameter(Mandatory = true, + Position = 1, + ParameterSetName = ByAccountProfileNameParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The certificate profile name of Azure CodeSigning account.")] + [ValidateNotNullOrEmpty()] + public string ProfileName { get; set; } + [Parameter(Mandatory = true, + Position = 2, + ParameterSetName = ByAccountProfileNameParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The endpoint url used to submit request to Azure CodeSigning.")] + public string EndpointUrl { get; set; } + + + /// + /// Metadata File Path + /// + [Parameter(Mandatory = true, + Position = 0, + ParameterSetName = ByMetadataFileParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Metadata File path. Cmdlet constructs the FQDN of an account profile based on the Metadata File and currently selected environment.")] + [ValidateNotNullOrEmpty] + public string MetadataFilePath { get; set; } + + [Parameter(Mandatory = true, + Position = 3, + ParameterSetName = ByAccountProfileNameParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Downloaded Root Cert file full path, including file name")] + [Parameter(Mandatory = true, + Position = 1, + ParameterSetName = ByMetadataFileParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Downloaded Root Cert file full path, including file name")] + [ValidateNotNullOrEmpty] + public string Destination { get; set; } + #endregion + + public override void ExecuteCmdlet() + { + Stream rootcert; + + if (!string.IsNullOrEmpty(AccountName)) + { + rootcert = CodeSigningServiceClient.GetCodeSigningRootCert(AccountName, ProfileName, EndpointUrl); + WriteRootCert(rootcert); + } + else if (!string.IsNullOrEmpty(MetadataFilePath)) + { + rootcert = CodeSigningServiceClient.GetCodeSigningRootCert(MetadataFilePath); + WriteRootCert(rootcert); + } + } + private void WriteRootCert(Stream rootcert) + { + var downloadPath = ResolvePath(Destination); + + var fileStream = new FileStream(downloadPath, FileMode.Create, FileAccess.Write); + rootcert.CopyTo(fileStream); + fileStream.Dispose(); + + //read thumbprint and subject namme + byte[] rawData = File.ReadAllBytes(downloadPath); + X509Certificate2 x509 = new X509Certificate2(rawData); + + WriteObject(downloadPath.Replace("\\", @"\")); + + PSSigningCertificate pscert = new PSSigningCertificate + { + Subject = x509.Subject, + Thumbprint = x509.Thumbprint + }; + + WriteObject(pscert, false); + } + } +} diff --git a/src/CodeSigning/CodeSigning/Commands/InvokeCIPolicySigning.cs b/src/CodeSigning/CodeSigning/Commands/InvokeCIPolicySigning.cs new file mode 100644 index 000000000000..fc4b1bc8ecc9 --- /dev/null +++ b/src/CodeSigning/CodeSigning/Commands/InvokeCIPolicySigning.cs @@ -0,0 +1,180 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using Microsoft.Azure.Commands.CodeSigning.Helpers; +using Microsoft.Azure.Commands.CodeSigning.Models; +using System; +using System.IO; +using System.Management.Automation; +using System.Xml.Linq; + +namespace Microsoft.Azure.Commands.CodeSigning +{ + [Cmdlet(VerbsLifecycle.Invoke, ResourceManager.Common.AzureRMConstants.AzurePrefix + "CodeSigningCIPolicySigning", DefaultParameterSetName = ByAccountProfileNameParameterSet)] + [OutputType(typeof(string))] + public class InvokeCIPolicySigning : CodeSigningCmdletBase + { + #region Parameter Set Names + + private const string ByAccountProfileNameParameterSet = "ByAccountProfileNameParameterSet"; + private const string ByMetadataFileParameterSet = "ByMetadataFileParameterSet"; + /// + /// The root element inside an SI Policy XML file. + /// + private static readonly XName SiPolicyRootElementName = XName.Get("SiPolicy", "urn:schemas-microsoft-com:sipolicy"); + + #endregion + + #region Input Parameter Definitions + + /// + /// Account Profile name + /// + [Parameter(Mandatory = true, + Position = 0, + ParameterSetName = ByAccountProfileNameParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The account name of Azure CodeSigning.")] + [ValidateNotNullOrEmpty] + public string AccountName { get; set; } + + [Parameter(Mandatory = true, + Position = 1, + ParameterSetName = ByAccountProfileNameParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The certificate profile name of Azure CodeSigning account.")] + [ValidateNotNullOrEmpty()] + public string ProfileName { get; set; } + + [Parameter(Mandatory = true, + Position = 2, + ParameterSetName = ByAccountProfileNameParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The endpoint url used to submit request to Azure CodeSigning.")] + public string EndpointUrl { get; set; } + + + /// + /// Metadata File Path + /// + [Parameter(Mandatory = true, + Position = 0, + ParameterSetName = ByMetadataFileParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Metadata File path.")] + [ValidateNotNullOrEmpty] + public string MetadataFilePath { get; set; } + + //common parameters + [Parameter(Mandatory = true, + Position = 3, + ParameterSetName = ByAccountProfileNameParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Original unsigned CI policy file path.")] + [Parameter(Mandatory = true, + Position = 1, + ParameterSetName = ByMetadataFileParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Original unsigned CI policy file path.")] + [ValidateNotNullOrEmpty] + public string Path { get; set; } + + [Parameter(Mandatory = true, + ParameterSetName = ByAccountProfileNameParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Signed CI policy file path")] + [Parameter(Mandatory = true, + Position = 2, + ParameterSetName = ByMetadataFileParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Signed CI policy file path")] + [ValidateNotNullOrEmpty] + public string Destination { get; set; } + + [Parameter(Mandatory = false, + ParameterSetName = ByAccountProfileNameParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Time Stamper Url.")] + [Parameter(Mandatory = false, + Position = 3, + ParameterSetName = ByMetadataFileParameterSet, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Time Stamper Url.")] + public string TimeStamperUrl { get; set; } + + #endregion + + public override void ExecuteCmdlet() + { + ValidateFileType(ResolvePath(Path)); + + + WriteMessage("CI Policy signing in progress...."); + + if (!string.IsNullOrEmpty(AccountName)) + { + CodeSigningServiceClient.SubmitCIPolicySigning(AccountName, ProfileName, EndpointUrl, ResolvePath(Path), ResolvePath(Destination), TimeStamperUrl); + } + else if (!string.IsNullOrEmpty(MetadataFilePath)) + { + CodeSigningServiceClient.SubmitCIPolicySigning(MetadataFilePath, ResolvePath(Path), ResolvePath(Destination), TimeStamperUrl); + } + + WriteMessage("CI Policy is successfully signed. " + ResolvePath(Destination)); + } + private void WriteMessage(string message) + { + WriteObject(message); + } + + private void ValidateFileType(string fullInPath) + { + if (System.IO.Path.GetExtension(fullInPath).ToLower() == ".bin") + { + WriteMessage(Environment.NewLine); + WriteMessage("CI Policy file submitted"); + } + else + { + // Is this an XML policy file? + // We can display a better error message here. + XDocument doc; + try + { + var fullInContents = File.ReadAllBytes(fullInPath); + doc = XmlUtil.SafeLoadXDocument(fullInContents); + } + catch + { + doc = null; + } + + if (doc?.Root.Name == SiPolicyRootElementName) + { + WriteWarning("Input file is an XML-based policy file."); + WriteWarning("Please run 'ConvertFrom-CiPolicy' to create a .bin file before running this command."); + } + + try + { + throw new InvalidOperationException($"File '{fullInPath}' is of a type that cannot be signed."); + } + catch (Exception ex) + { + throw new TerminatingErrorException(ex, ErrorCategory.InvalidData); + } + } + } + } +} diff --git a/src/CodeSigning/CodeSigning/Helpers/CmsSigner.cs b/src/CodeSigning/CodeSigning/Helpers/CmsSigner.cs new file mode 100644 index 000000000000..954198a30c49 --- /dev/null +++ b/src/CodeSigning/CodeSigning/Helpers/CmsSigner.cs @@ -0,0 +1,93 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using Azure.CodeSigning.Client.CryptoProvider; +using Azure.Core; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.Pkcs; + +namespace Microsoft.Azure.Commands.CodeSigning.Helpers +{ + internal class CmsSigner + { + public CmsSigner() { } + public void SignCIPolicy(TokenCredential tokenCred, string accountName, string certProfile, + string endpointUrl, string unsignedCIFilePath, string signedCIFilePath, string timeStamperUrl) + { + int retry = 5; + + while (retry > 0) + { + try + { + var context = new AzCodeSignContext(tokenCred, accountName, certProfile, endpointUrl); + + var cert = context.InitializeChainAsync().Result; + RSA rsa = new RSAAzCodeSign(context); + + var cipolicy = File.ReadAllBytes(unsignedCIFilePath); + var cmscontent = new ContentInfo(new Oid("1.3.6.1.4.1.311.79.1"), cipolicy); + var cms = new SignedCms(cmscontent, false); + + var signer = new System.Security.Cryptography.Pkcs.CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, cert, rsa); + cms.ComputeSignature(signer); + + cms.CheckSignature(true); + //Console.WriteLine(Util.BytesToHex(cms.Encode(), " ", 16)); + + var signedData = cms.Encode(); + + if (!string.IsNullOrWhiteSpace(timeStamperUrl)) + { + var timestampingUri = new Uri("http://www.microsoft.com"); + try + { + timestampingUri = new Uri(timeStamperUrl); + + var signedAndTimestampedFullFileContents = TimeStampingHelper.Rfc3161Timestamp( + input: signedData, + timestampServerUrl: timestampingUri.ToString()); + + if (signedAndTimestampedFullFileContents == null) + { + throw new Exception("Timestamping failed. "); + } + + File.WriteAllBytes(signedCIFilePath, signedAndTimestampedFullFileContents); ; + } + catch + { + throw new Exception("Input TimeStamperUrl is not valid Uri. Please check."); + } + } + else + { + File.WriteAllBytes(signedCIFilePath, signedData); + } + return; + } + catch(Exception ex) + { + retry--; + if (retry == 0 || ex.Message == "Input TimeStamperUrl is not valid Uri. Please check.") + { + throw ex; + } + } + } + } + } +} diff --git a/src/CodeSigning/CodeSigning/Helpers/NativeMethod.cs b/src/CodeSigning/CodeSigning/Helpers/NativeMethod.cs new file mode 100644 index 000000000000..f7fe80adf31d --- /dev/null +++ b/src/CodeSigning/CodeSigning/Helpers/NativeMethod.cs @@ -0,0 +1,345 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using Microsoft.Win32.SafeHandles; +using System; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security; +using System.Runtime.CompilerServices; + +namespace Microsoft.Azure.Commands.CodeSigning.Helpers +{ + [SuppressUnmanagedCodeSecurity] + internal unsafe static class NativeMethod + { + private const string CRYPT32_LIB = "crypt32.dll"; + /* + * CRYPT32.DLL + */ + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa380228(v=vs.85).aspx + [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + public static extern SafeCryptMessageHandle CryptMsgOpenToDecode( + [In] MessageEncodingType dwMsgEncodingType, + [In] uint dwFlags, + [In] MessageType dwMsgType, + [In] IntPtr hCryptProv, + [In] IntPtr pRecipientInfo, + [In] IntPtr pStreamInfo); + + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa380229(v=vs.85).aspx + [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + public static extern SafeCryptMessageHandle CryptMsgOpenToEncode( + [In] MessageEncodingType dwMsgEncodingType, + [In] uint dwFlags, + [In] MessageType dwMsgType, + [In] IntPtr pvMsgEncodeInfo, + [In, MarshalAs(UnmanagedType.LPStr)] string pszInnerContentObjID, + [In] IntPtr pStreamInfo); + + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa380231(v=vs.85).aspx + [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + public static extern bool CryptMsgUpdate( + [In] SafeCryptMessageHandle hCryptMsg, + [In] IntPtr pbData, + [In] uint cbData, + [In] bool fFinal); + + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa380216(v=vs.85).aspx + [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static extern void CryptMemFree( + [In] IntPtr pv); + + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd433803(v=vs.85).aspx + [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + public static extern bool CryptRetrieveTimeStamp( + [In, MarshalAs(UnmanagedType.LPWStr)] string wszUrl, + [In] uint dwRetrievalFlags, + [In] uint dwTimeout, + [In, MarshalAs(UnmanagedType.LPStr)] string pszHashId, + [In] IntPtr pPara, + [In] byte* pbData, + [In] uint cbData, + [Out] out SafeCryptMemHandle ppTsContext, + [In] IntPtr ppTsSigner, + [In] IntPtr phStore); + + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa380220(v=vs.85).aspx + [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + public static extern bool CryptMsgControl( + [In] SafeCryptMessageHandle hCryptMsg, + [In] uint dwFlags, + [In] uint dwCtrlType, + [In] void* pvCtrlPara); + + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa380227(v=vs.85).aspx + [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + public static extern bool CryptMsgGetParam( + [In] SafeCryptMessageHandle hCryptMsg, + [In] uint dwParamType, + [In] uint dwIndex, + [In] IntPtr pvData, + [In, Out] ref uint pcbData); + + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa379922(v=vs.85).aspx + [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + public static extern bool CryptEncodeObjectEx( + [In] MessageEncodingType dwCertEncodingType, + [In] IntPtr lpszStructType, + [In] IntPtr pvStructInfo, + [In] uint dwFlags, + [In] IntPtr pEncodePara, + [Out] out SafeLocalAllocHandle pvEncoded, + [Out] out uint pcbEncoded); + + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa380219(v=vs.85).aspx + [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static extern bool CryptMsgClose( + [In] IntPtr hCryptMsg); + } + + internal sealed class SafeCryptMemHandle : SafeHandleZeroOrMinusOneIsInvalid + { + // Called by P/Invoke when returning SafeHandles + private SafeCryptMemHandle() + : base(ownsHandle: true) + { } + + // Do not provide a finalizer - SafeHandle's critical finalizer will + // call ReleaseHandle for you. + + protected override bool ReleaseHandle() + { + NativeMethod.CryptMemFree(handle); + return true; + } + } + + internal sealed class SafeLocalAllocHandle : SafeHandleZeroOrMinusOneIsInvalid + { + // Called by P/Invoke when returning SafeHandles + private SafeLocalAllocHandle() + : base(ownsHandle: true) + { } + + public static SafeLocalAllocHandle Allocate(uint cb) + { + var newHandle = new SafeLocalAllocHandle(); + newHandle.AllocateCore(cb); + return newHandle; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + private void AllocateCore(uint cb) + { + SetHandle(Marshal.AllocHGlobal((IntPtr)cb)); + } + + // Do not provide a finalizer - SafeHandle's critical finalizer will + // call ReleaseHandle for you. + + protected override bool ReleaseHandle() + { + // misnomer - actually calls LocalFree + Marshal.FreeHGlobal(handle); + return true; + } + + public byte[] ToByteArray(uint byteCount) + { + byte[] buffer = new byte[byteCount]; + if (byteCount != 0) + { + bool refAdded = false; + + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + DangerousAddRef(ref refAdded); + Marshal.Copy(DangerousGetHandle(), buffer, 0, buffer.Length); + } + finally + { + if (refAdded) + { + DangerousRelease(); + } + } + } + + return buffer; + } + } + + internal unsafe static class CapiUtil + { + /// + /// Asserts that has been satisfied. + /// + /// Thrown if is not satisfied. + /// + /// This isn't a typical Debug.Assert; the check is always performed, even in retail builds. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AssertCondition(bool condition, string message) + { + if (!condition) + { + Fail(message); + } + } + + /// + /// Asserts that the operation returned successfully. + /// + /// Thrown if is false. + /// + /// This isn't a typical Debug.Assert; the check is always performed, even in retail builds. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + public static void AssertSuccess(bool success, string apiName) + { + if (!success) + { + int errorCode = Marshal.GetLastWin32Error(); + if (errorCode != 0) + { + throw new CryptographicException(errorCode); + } + else + { + throw new CryptographicException($"Function {apiName} returned failure. No error code provided."); + } + } + } + + /// + /// Asserts that the provided is valid. + /// + /// Thrown if is invalid. + /// + /// This isn't a typical Debug.Assert; the check is always performed, even in retail builds. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + public static void AssertSafeHandleIsValid(T safeHandle) where T : SafeHandle + { + if (safeHandle == null || safeHandle.IsInvalid) + { + int errorCode = Marshal.GetLastWin32Error(); + if (errorCode != 0) + { + throw new CryptographicException(errorCode); + } + else + { + throw new CryptographicException($"Function did not return a valid {typeof(T).Name}. No error code provided."); + } + } + } + + /// + /// Throws a with the provided fail message. + /// + /// + /// This isn't a typical Debug.Fail; the check is always performed, even in retail builds. + /// This method doesn't actually return (return type O), but it's typed to return Exception + /// so that callers can write 'throw Fail' in order to pass verification. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception Fail(string message) + { + throw new CryptographicException($"Assertion failed: {message}"); + } + } + + [Flags] + internal enum MessageEncodingType : uint + { + X509_ASN_ENCODING = 0x00000001, + PKCS_7_ASN_ENCODING = 0x00010000, + } + + // from wincrypt.h + internal enum MessageType : uint + { + CMSG_DATA = 1, + CMSG_SIGNED = 2, + CMSG_ENVELOPED = 3, + CMSG_SIGNED_AND_ENVELOPED = 4, + CMSG_HASHED = 5, + CMSG_ENCRYPTED = 6, + } + + // from wincrypt.h + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa377807(v=vs.85).aspx + [StructLayout(LayoutKind.Sequential)] + internal struct CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR_PARA + { + internal uint cbSize; + internal uint dwSignerIndex; + internal DATA_BLOB BLOB; + } + + // from wincrypt.h + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd433837(v=vs.85).aspx + [StructLayout(LayoutKind.Sequential)] + internal struct CRYPT_TIMESTAMP_CONTEXT + { + internal uint cbEncoded; + internal IntPtr pbEncoded; + internal IntPtr pTimeStamp; + } + + // from wincrypt.h + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa381414(v=vs.85).aspx + [StructLayout(LayoutKind.Sequential)] + internal struct DATA_BLOB + { + internal uint cbData; + internal IntPtr pbData; + } + + // from wincrypt.h + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa381146(v=vs.85).aspx + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct CRYPT_ATTRIBUTES + { + internal uint cAttr; + internal CRYPT_ATTRIBUTE* rgAttr; + } + + // from wincrypt.h + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa381139(v=vs.85).aspx + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct CRYPT_ATTRIBUTE + { + internal IntPtr pszObjId; + internal uint cValue; + internal DATA_BLOB* rgValue; + } + + // from wincrypt.h + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa377812(v=vs.85).aspx + [StructLayout(LayoutKind.Sequential)] + internal struct CMSG_CTRL_DEL_SIGNER_UNAUTH_ATTR_PARA + { + internal uint cbSize; + internal uint dwSignerIndex; + internal uint dwUnauthAttrIndex; + } +} + diff --git a/src/CodeSigning/CodeSigning/Helpers/TerminatingErrorException.cs b/src/CodeSigning/CodeSigning/Helpers/TerminatingErrorException.cs new file mode 100644 index 000000000000..3780e896ad77 --- /dev/null +++ b/src/CodeSigning/CodeSigning/Helpers/TerminatingErrorException.cs @@ -0,0 +1,32 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Text; + +namespace Microsoft.Azure.Commands.CodeSigning.Helpers +{ + internal class TerminatingErrorException : Exception + { + public TerminatingErrorException(Exception innerException, ErrorCategory category) + : base(innerException.Message, innerException) + { + Category = category; + } + + public ErrorCategory Category { get; } + } +} diff --git a/src/CodeSigning/CodeSigning/Helpers/TimeStampingHelper.cs b/src/CodeSigning/CodeSigning/Helpers/TimeStampingHelper.cs new file mode 100644 index 000000000000..0090bee484ac --- /dev/null +++ b/src/CodeSigning/CodeSigning/Helpers/TimeStampingHelper.cs @@ -0,0 +1,317 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using Microsoft.Win32.SafeHandles; +using static Microsoft.Azure.Commands.CodeSigning.Helpers.NativeMethod; +using static Microsoft.Azure.Commands.CodeSigning.Helpers.CapiUtil; + +namespace Microsoft.Azure.Commands.CodeSigning.Helpers +{ + public unsafe static class TimeStampingHelper + { + private static readonly Oid _rfc3161CountersignOid = new Oid("1.3.6.1.4.1.311.3.3.1"); + internal static readonly Oid Sha384RsaOid = new Oid("1.2.840.113549.1.1.12"); + + private static readonly Oid _rsaCountersignOid = new Oid("1.2.840.113549.1.9.6"); + private static readonly Oid _sha256Oid = new Oid("2.16.840.1.101.3.4.2.1"); + + // constants from wincrypt.h + private static readonly IntPtr PKCS_ATTRIBUTE = (IntPtr)22; + private static readonly IntPtr X509_OCTET_STRING = (IntPtr)25; + private const uint CRYPT_ENCODE_ALLOC_FLAG = 0x8000; + private const uint CMSG_CONTENT_PARAM = 2; + private const uint CMSG_SIGNER_INFO_PARAM = 6; + private const uint CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR = 8; + private const uint CMSG_CTRL_DEL_SIGNER_UNAUTH_ATTR = 9; + private const uint CMSG_SIGNER_UNAUTH_ATTR_PARAM = 10; + private const uint CMSG_COMPUTED_HASH_PARAM = 22; + private const uint CMSG_ENCRYPTED_DIGEST = 27; + private const uint CMSG_ENCODED_MESSAGE = 29; + private const uint CMSG_SIGNED_DATA_NO_SIGN_FLAG = 0x00000080; + private const uint CMSG_CTRL_DEL_SIGNER = 7; + private const uint CMSG_CTRL_ADD_CMS_SIGNER_INFO = 20; + + /// + /// Timestamps an input file. + /// + public static byte[] Rfc3161Timestamp(byte[] input, string timestampServerUrl) + { + // precondition checking + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + if (timestampServerUrl == null) + { + throw new ArgumentNullException(nameof(timestampServerUrl)); + } + + fixed (byte* pbInput = input) + { + byte dummy; // since the CLR doesn't like pinning empty arrays + return Rfc3161TimestampCore( + pbInput: (pbInput != null) ? pbInput : &dummy, + cbInput: (uint)input.Length, + timestampServerUrl: timestampServerUrl); + } + } + + private static byte[] Rfc3161TimestampCore(byte* pbInput, uint cbInput, string timestampServerUrl) + { + // This logic closely matches that of Rfc3161Timestamper::TimestampPkcs7 + // See //depot/fbl_sec/ds/security/cryptoapi/pkisign/tools/capilib/SignedCode.cpp + + // parse the incoming CMS payload + + SafeCryptMessageHandle messageHandle = CryptMsgOpenToDecode( + dwMsgEncodingType: MessageEncodingType.X509_ASN_ENCODING | MessageEncodingType.PKCS_7_ASN_ENCODING, + dwFlags: 0, + dwMsgType: 0, + hCryptProv: IntPtr.Zero, + pRecipientInfo: IntPtr.Zero, + pStreamInfo: IntPtr.Zero); + AssertSafeHandleIsValid(messageHandle); + + using (messageHandle) + { + bool success = CryptMsgUpdate( + hCryptMsg: messageHandle, + pbData: (IntPtr)pbInput, + cbData: cbInput, + fFinal: true); + AssertSuccess(success, nameof(CryptMsgUpdate)); + + // get the existing signed digest from the message and send it up to the timestamper + + byte[] digest = GetMessageEncryptedDigest(messageHandle); + SafeCryptMemHandle timestampStructHandle; + fixed (byte* pbDigest = digest) + { + success = CryptRetrieveTimeStamp( + wszUrl: timestampServerUrl, + dwRetrievalFlags: 0, + dwTimeout: 1000 * 60 * 5, // 5 minutes + pszHashId: _sha256Oid.Value, + pPara: IntPtr.Zero, + pbData: pbDigest, + cbData: (uint)digest.Length, + ppTsContext: out timestampStructHandle, + ppTsSigner: IntPtr.Zero, + phStore: IntPtr.Zero); + } + AssertSuccess(success, nameof(CryptRetrieveTimeStamp)); + AssertSafeHandleIsValid(timestampStructHandle); + + // turn the timestamped payload into a PKCS attribute + + SafeLocalAllocHandle timestampAttributeHandle; + uint cbTimestampAttribute; + using (timestampStructHandle) + { + CRYPT_TIMESTAMP_CONTEXT* pTimestampContext = (CRYPT_TIMESTAMP_CONTEXT*)timestampStructHandle.DangerousGetHandle(); + timestampAttributeHandle = EncodePkcsAttribute( + oid: _rfc3161CountersignOid, + pbData: pTimestampContext->pbEncoded, + cbData: pTimestampContext->cbEncoded, + cbEncoded: out cbTimestampAttribute); + } + + using (timestampAttributeHandle) + { + // set our timestamp as the sole countersignature + + DeleteAllCountersignatures(messageHandle); + + CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR_PARA attrToAdd = default(CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR_PARA); + attrToAdd.cbSize = (uint)sizeof(CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR_PARA); + attrToAdd.dwSignerIndex = 0; // always assume only one signer + attrToAdd.BLOB.pbData = timestampAttributeHandle.DangerousGetHandle(); + attrToAdd.BLOB.cbData = cbTimestampAttribute; + + success = CryptMsgControl( + hCryptMsg: messageHandle, + dwFlags: 0, + dwCtrlType: CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR, + pvCtrlPara: &attrToAdd); + AssertSuccess(success, nameof(CryptMsgControl)); + + // that's it! + + return GetMessageEncodedMessage(messageHandle); + } + } + } + + internal static byte[] GetMessageEncodedMessage(SafeCryptMessageHandle message) + { + return GetMessageParameterAsByteArray( + message: message, + dwParamType: CMSG_ENCODED_MESSAGE, + dwIndex: 0); + } + + private static SafeLocalAllocHandle EncodePkcsAttribute(Oid oid, IntPtr pbData, uint cbData, out uint cbEncoded) + { + IntPtr pszOid = Marshal.StringToHGlobalAnsi(oid.Value); + try + { + DATA_BLOB blob = default(DATA_BLOB); + blob.pbData = pbData; + blob.cbData = cbData; + + CRYPT_ATTRIBUTE attribute = default(CRYPT_ATTRIBUTE); + attribute.pszObjId = pszOid; + attribute.cValue = 1; + attribute.rgValue = &blob; + + SafeLocalAllocHandle retVal; + bool success = CryptEncodeObjectEx( + dwCertEncodingType: MessageEncodingType.X509_ASN_ENCODING | MessageEncodingType.PKCS_7_ASN_ENCODING, + lpszStructType: PKCS_ATTRIBUTE, + pvStructInfo: (IntPtr)(&attribute), + dwFlags: CRYPT_ENCODE_ALLOC_FLAG, + pEncodePara: IntPtr.Zero, + pvEncoded: out retVal, + pcbEncoded: out cbEncoded); + AssertSuccess(success, nameof(CryptEncodeObjectEx)); + AssertSafeHandleIsValid(retVal); + + return retVal; + } + finally + { + Marshal.FreeHGlobal(pszOid); + } + } + + internal static SafeLocalAllocHandle GetMessageSignerUnauthenticatedAttributes(SafeCryptMessageHandle message) + { + uint unused; + return GetMessageParameterAsHandle( + message: message, + dwParamType: CMSG_SIGNER_UNAUTH_ATTR_PARAM, + dwIndex: 0, + cbData: out unused, + allowFailure: true); + } + + private static void DeleteAllCountersignatures(SafeCryptMessageHandle message) + { + SafeLocalAllocHandle attributesHandle = GetMessageSignerUnauthenticatedAttributes(message); + + // if there are no existing unauth attributes, short-circuit the method now + if (attributesHandle == null) + { + return; + } + + using (attributesHandle) + { + CRYPT_ATTRIBUTES* pCryptAttributes = (CRYPT_ATTRIBUTES*)attributesHandle.DangerousGetHandle(); + + // iterate through the array backward since data after the deleted index shifts forward + for (uint i = pCryptAttributes->cAttr; i-- != 0;) + { + string thisAttrOid = Marshal.PtrToStringAnsi(pCryptAttributes->rgAttr[i].pszObjId); + if (thisAttrOid == _rsaCountersignOid.Value || thisAttrOid == _rfc3161CountersignOid.Value) + { + // delete the attribute at 'i' index if the OID matches a countersignature we want to remove + CMSG_CTRL_DEL_SIGNER_UNAUTH_ATTR_PARA attrToDelete = default(CMSG_CTRL_DEL_SIGNER_UNAUTH_ATTR_PARA); + attrToDelete.cbSize = (uint)sizeof(CMSG_CTRL_DEL_SIGNER_UNAUTH_ATTR_PARA); + attrToDelete.dwSignerIndex = 0; // always assume only one signer + attrToDelete.dwUnauthAttrIndex = i; + + bool success = CryptMsgControl( + hCryptMsg: message, + dwFlags: 0, + dwCtrlType: CMSG_CTRL_DEL_SIGNER_UNAUTH_ATTR, + pvCtrlPara: &attrToDelete); + AssertSuccess(success, nameof(CryptMsgControl)); + } + } + } + } + + internal static byte[] GetMessageEncryptedDigest(SafeCryptMessageHandle message) + { + return GetMessageParameterAsByteArray( + message: message, + dwParamType: CMSG_ENCRYPTED_DIGEST, + dwIndex: 0); + } + + private static byte[] GetMessageParameterAsByteArray(SafeCryptMessageHandle message, uint dwParamType, uint dwIndex) + { + uint cbData; + using (var buffer = GetMessageParameterAsHandle(message, dwParamType, dwIndex, out cbData)) + { + // trim and return + return buffer.ToByteArray(cbData); + } + } + + private static SafeLocalAllocHandle GetMessageParameterAsHandle(SafeCryptMessageHandle message, uint dwParamType, uint dwIndex, out uint cbData, bool allowFailure = false) + { + // first, determine the number of bytes needed to allocate + uint cbDataTemp = 0; + bool success = CryptMsgGetParam( + hCryptMsg: message, + dwParamType: dwParamType, + dwIndex: dwIndex, + pvData: IntPtr.Zero, + pcbData: ref cbDataTemp); + + // it's ok if we can't get certain parameters - don't throw + if (!success && allowFailure) + { + cbData = 0; + return null; + } + AssertSuccess(success, nameof(CryptMsgGetParam)); + + // allocate necessary amount of memory (this might be overestimated) + // and rerun the operation + var buffer = SafeLocalAllocHandle.Allocate(cbDataTemp); + success = CryptMsgGetParam( + hCryptMsg: message, + dwParamType: dwParamType, + dwIndex: dwIndex, + pvData: (IntPtr)buffer.DangerousGetHandle(), + pcbData: ref cbDataTemp); + AssertSuccess(success, nameof(CryptMsgGetParam)); + + cbData = cbDataTemp; + return buffer; + } + } + + internal sealed class SafeCryptMessageHandle : SafeHandleZeroOrMinusOneIsInvalid + { + // Called by P/Invoke when returning SafeHandles + private SafeCryptMessageHandle() + : base(ownsHandle: true) + { } + + // Do not provide a finalizer - SafeHandle's critical finalizer will + // call ReleaseHandle for you. + + protected override bool ReleaseHandle() + { + return NativeMethod.CryptMsgClose(handle); + } + } +} diff --git a/src/CodeSigning/CodeSigning/Helpers/XmlUtil.cs b/src/CodeSigning/CodeSigning/Helpers/XmlUtil.cs new file mode 100644 index 000000000000..e70d8a10d684 --- /dev/null +++ b/src/CodeSigning/CodeSigning/Helpers/XmlUtil.cs @@ -0,0 +1,66 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System.IO; +using System.Xml.Linq; +using System.Xml; + +namespace Microsoft.Azure.Commands.CodeSigning.Helpers +{ + internal static class XmlUtil + { + /// + /// Creates an from an XML string per SDL 7.0 guidance. + /// + public static XDocument SafeLoadXDocument(byte[] xmlFile) + { + var settings = new XmlReaderSettings() + { + DtdProcessing = DtdProcessing.Prohibit, + IgnoreProcessingInstructions = true, + XmlResolver = null + }; + + using (var reader = XmlReader.Create(new MemoryStream(xmlFile), settings)) + { + return XDocument.Load(reader); + } + } + + /// + /// Creates an from an XML string per SDL 7.0 guidance. + /// + public static XmlDocument SafeLoadXmlDocument(string xml) + { + XmlDocument document = new XmlDocument() + { + XmlResolver = null + }; + + var settings = new XmlReaderSettings() + { + DtdProcessing = DtdProcessing.Prohibit, + IgnoreProcessingInstructions = true, + XmlResolver = null + }; + + using (var reader = XmlReader.Create(new StringReader(xml), settings)) + { + document.Load(reader); + } + + return document; + } + } +} diff --git a/src/CodeSigning/CodeSigning/Models/CodeSigningCmdletBase.cs b/src/CodeSigning/CodeSigning/Models/CodeSigningCmdletBase.cs new file mode 100644 index 000000000000..c0b0b69827ab --- /dev/null +++ b/src/CodeSigning/CodeSigning/Models/CodeSigningCmdletBase.cs @@ -0,0 +1,113 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Management.Automation; +using Azure.Core.Diagnostics; +using Microsoft.Azure.Commands.Common.Authentication; +using Microsoft.Azure.Commands.ResourceManager.Common; + +namespace Microsoft.Azure.Commands.CodeSigning.Models +{ + public class CodeSigningCmdletBase : AzureRMCmdlet + { + public static readonly DateTime EpochDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private AzureEventSourceListener _azureEventSourceListener; + + internal ICodeSigningServiceClient CodeSigningServiceClient + { + get + { + if (codeSigningServiceClient == null) + { + this.codeSigningServiceClient = new CodeSigningServiceClient( + AzureSession.Instance.AuthenticationFactory, + DefaultContext); + } + + return this.codeSigningServiceClient; + } + set + { + this.codeSigningServiceClient = value; + } + } + + protected string ResolvePath(string path) + { + // See http://stackoverflow.com/a/8506768/59641 for a good primer + // on how to resolve paths in Powershell cmdlets. + string basePath = SessionState.Path.CurrentFileSystemLocation.ProviderPath; + string fullPath = Path.Combine(basePath, path); + return Path.GetFullPath(fullPath); // normalize + } + + protected string GetDefaultFileForOperation(string operationName, string vaultName, string entityName) + { + // caller is responsible for parameter validation + var currentPath = CurrentPath(); + var filename = string.Format("{0}\\{1}-{2}-{3}", currentPath, vaultName, entityName, DateTime.UtcNow.Subtract(EpochDate).TotalSeconds); + + return filename; + } + + private ICodeSigningServiceClient codeSigningServiceClient; + + public List ResourceWildcardFilter(string name, IEnumerable resources) + { + if (!string.IsNullOrEmpty(name)) + { + IEnumerable output = resources; + WildcardPattern pattern = new WildcardPattern(name, WildcardOptions.IgnoreCase); + output = output.Where(t => IsMatch(t, "Name", pattern)); + + return output.ToList(); + } + else + { + return resources.ToList(); + } + } + + private bool IsMatch(T resource, string property, WildcardPattern pattern) + { + var value = (string)GetPropertyValue(resource, property); + return !string.IsNullOrEmpty(value) && pattern.IsMatch(value); + } + + private object GetPropertyValue(T resource, string property) + { + System.Reflection.PropertyInfo pi = typeof(T).GetProperty(property); + if (pi != null) + { + return pi.GetValue(resource, null); + } + + return null; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing && _azureEventSourceListener != null) + { + _azureEventSourceListener.Dispose(); + _azureEventSourceListener = null; + } + } + } +} diff --git a/src/CodeSigning/CodeSigning/Models/CodeSigningServiceClient.cs b/src/CodeSigning/CodeSigning/Models/CodeSigningServiceClient.cs new file mode 100644 index 000000000000..e05bdf8dc387 --- /dev/null +++ b/src/CodeSigning/CodeSigning/Models/CodeSigningServiceClient.cs @@ -0,0 +1,142 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using Azure.CodeSigning; +using Microsoft.Azure.Commands.Common.Authentication.Abstractions; +using System.IO; +using Azure.Core; +using Microsoft.Azure.Commands.CodeSigning.Helpers; +using Newtonsoft.Json; +using System.Linq; + +namespace Microsoft.Azure.Commands.CodeSigning.Models +{ + internal class CodeSigningServiceClient : ICodeSigningServiceClient + { + /// + /// Parameterless constructor for Mocking. + /// + public CodeSigningServiceClient() + { + } + private CertificateProfileClient CertificateProfileClient { get; set; } + private Metadata Metadata { get; set; } + + TokenCredential user_creds; + + public CodeSigningServiceClient(IAuthenticationFactory authFactory, IAzureContext context) + { + if (authFactory == null) + throw new ArgumentNullException(nameof(authFactory)); + if (context == null) + throw new ArgumentNullException(nameof(context)); + if (context.Environment == null) + //throw new ArgumentException(KeyVaultProperties.Resources.InvalidAzureEnvironment); + throw new ArgumentException("Invalid Environment"); + + Initialize(authFactory, context); + } + + private Exception GetInnerException(Exception exception) + { + while (exception.InnerException != null) exception = exception.InnerException; + //if need modify inner exception + //if (exception is KeyVaultErrorException kvEx && kvEx?.Body?.Error != null) + //{ + // var detailedMsg = exception.Message; + // detailedMsg += string.Format(Environment.NewLine + "Code: {0}", kvEx.Body.Error.Code); + // detailedMsg += string.Format(Environment.NewLine + "Message: {0}", kvEx.Body.Error.Message); + // exception = new KeyVaultErrorException(detailedMsg, kvEx); + //} + return exception; + } + + private void Initialize(IAuthenticationFactory authFactory, IAzureContext context) + { + user_creds = new UserSuppliedCredential(new CodeSigningServiceCredential(authFactory, context, "api://cf2ab426-f71a-4b61-bb8a-9e505b85bc2e")); + } + + private void GetCertificateProfileClient(string endpoint) + { + var options = new CertificateProfileClientOptions(); + options.Diagnostics.IsTelemetryEnabled = false; + options.Diagnostics.IsLoggingEnabled = false; + options.Diagnostics.IsLoggingContentEnabled = false; + + CertificateProfileClient = new CertificateProfileClient( + user_creds, + new Uri(endpoint), + options); + } + + public string[] GetCodeSigningEku(string accountName, string profileName, string endpoint) + { + GetCertificateProfileClient(endpoint); + + var eku = CertificateProfileClient.GetSignEku(accountName, profileName); + return eku.Value?.ToArray(); + } + public string[] GetCodeSigningEku(string metadataPath) + { + var rawMetadata = File.ReadAllText(metadataPath); + Metadata = JsonConvert.DeserializeObject(rawMetadata); + + var accountName = Metadata.CodeSigningAccountName; + var profileName = Metadata.CertificateProfileName; + var endpoint = Metadata.Endpoint; + + return GetCodeSigningEku(accountName, profileName, endpoint); + } + + public Stream GetCodeSigningRootCert(string accountName, string profileName, string endpoint) + { + GetCertificateProfileClient(endpoint); + + var rootCert = CertificateProfileClient.GetSignCertificateRoot(accountName, profileName); + return rootCert; + } + + public Stream GetCodeSigningRootCert(string metadataPath) + { + var rawMetadata = File.ReadAllText(metadataPath); + Metadata = JsonConvert.DeserializeObject(rawMetadata); + + var accountName = Metadata.CodeSigningAccountName; + var profileName = Metadata.CertificateProfileName; + var endpoint = Metadata.Endpoint; + + return GetCodeSigningRootCert(accountName, profileName, endpoint); + } + + public void SubmitCIPolicySigning(string accountName, string profileName, string endpoint, + string unsignedCIFilePath, string signedCIFilePath, string timeStamperUrl = null) + { + var cipolicySigner = new CmsSigner(); + cipolicySigner.SignCIPolicy(user_creds, accountName, profileName, endpoint, unsignedCIFilePath, signedCIFilePath, timeStamperUrl); + } + + public void SubmitCIPolicySigning(string metadataPath, string unsignedCIFilePath, string signedCIFilePath, string timeStamperUrl) + { + var rawMetadata = File.ReadAllText(metadataPath); + Metadata = JsonConvert.DeserializeObject(rawMetadata); + + var accountName = Metadata.CodeSigningAccountName; + var profileName = Metadata.CertificateProfileName; + var endpoint = Metadata.Endpoint; + + SubmitCIPolicySigning(accountName, profileName, endpoint, unsignedCIFilePath, signedCIFilePath, timeStamperUrl); + } + } +} diff --git a/src/CodeSigning/CodeSigning/Models/CodeSigningServiceCredential.cs b/src/CodeSigning/CodeSigning/Models/CodeSigningServiceCredential.cs new file mode 100644 index 000000000000..7c6681853268 --- /dev/null +++ b/src/CodeSigning/CodeSigning/Models/CodeSigningServiceCredential.cs @@ -0,0 +1,121 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using Microsoft.Azure.Commands.Common.Authentication; +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Azure.Commands.Common.Authentication.Abstractions; + +namespace Microsoft.Azure.Commands.CodeSigning.Models +{ + internal class CodeSigningServiceCredential + { + private readonly IAuthenticationFactory _authenticationFactory; + private readonly IAzureContext _context; + private readonly string _endpointName; + + public CodeSigningServiceCredential(IAuthenticationFactory authFactory, IAzureContext context, string resourceIdEndpoint) + { + if (authFactory == null) + throw new ArgumentNullException("authFactory"); + if (context == null) + throw new ArgumentNullException("context"); + _authenticationFactory = authFactory; + _context = context; + _endpointName = resourceIdEndpoint; + this.TenantId = GetTenantId(context); + } + + public string TenantId { get; private set; } + + /// + /// Authentication callback method required by KeyVaultClient + /// + /// + /// + /// + /// + public Task OnAuthentication(string authority, string resource, string scope) + { + var tokenStr = GetTokenInternal(this.TenantId, this._authenticationFactory, this._context, this._endpointName); + return Task.FromResult(tokenStr.ToString()); + } + + public string GetToken() + { + return GetAccessToken().AccessToken; + } + + public IAccessToken GetAccessToken() + { + return GetTokenInternal(TenantId, _authenticationFactory, _context,_endpointName); + } + + private static string GetTenantId(IAzureContext context) + { + if (context.Account == null) + { + //throw new ArgumentException(CodeSigningProperties.Resources.ArmAccountNotFound); + throw new ArgumentException("arm account not found"); + } + + var tenantId = string.Empty; + if (context.Tenant != null && context.Tenant.GetId() != Guid.Empty) + { + tenantId = context.Tenant.Id.ToString(); + } + else if (string.IsNullOrWhiteSpace(tenantId) && context.Subscription != null && context.Account != null) + { + tenantId = context.Subscription.GetPropertyAsArray(AzureSubscription.Property.Tenants) + .Intersect(context.Account.GetPropertyAsArray(AzureAccount.Property.Tenants)) + .FirstOrDefault(); + } + + return tenantId; + } + + private static IAccessToken GetTokenInternal(string tenantId, IAuthenticationFactory authFactory, IAzureContext context, string resourceIdEndpoint) + { + if (string.IsNullOrWhiteSpace(tenantId)) + //throw new ArgumentException(CodeSigningProperties.Resources.NoTenantInContext); + throw new ArgumentException("NoTenantInContext"); + + try + { + var tokenCache = AzureSession.Instance.TokenCache; + if (context.TokenCache != null && context.TokenCache.CacheData != null && context.TokenCache.CacheData.Length > 0) + { + tokenCache = context.TokenCache; + } + + var accesstoken = authFactory.Authenticate(context.Account, context.Environment, tenantId, null, ShowDialog.Never, + null, tokenCache, resourceIdEndpoint); + + if (context.TokenCache != null && context.TokenCache.CacheData != null && context.TokenCache.CacheData.Length > 0) + { + context.TokenCache = tokenCache; + } + + return accesstoken; + //return Tuple.Create(accesstoken, context.Environment.GetEndpoint(resourceIdEndpoint)); + } + catch (Exception ex) + { + //throw new ArgumentException(CodeSigningProperties.Resources.InvalidSubscriptionState, ex); + throw new ArgumentException("InvalidSubscriptionState",ex); + } + } + } +} diff --git a/src/CodeSigning/CodeSigning/Models/ICodeSigningServiceClient.cs b/src/CodeSigning/CodeSigning/Models/ICodeSigningServiceClient.cs new file mode 100644 index 000000000000..4cf522fd5cff --- /dev/null +++ b/src/CodeSigning/CodeSigning/Models/ICodeSigningServiceClient.cs @@ -0,0 +1,35 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System.IO; + +namespace Microsoft.Azure.Commands.CodeSigning.Models +{ + public interface ICodeSigningServiceClient + { + string[] GetCodeSigningEku(string accountName, string profileName, string endpoint); + + string[] GetCodeSigningEku(string metadataPath); + + Stream GetCodeSigningRootCert(string accountName, string profileName, string endpoint); + + Stream GetCodeSigningRootCert(string metadataPath); + + void SubmitCIPolicySigning(string accountName, string profileName, string endpoint, + string unsignedCIFilePath, string signedCIFilePath, string timeStamperUrl); + void SubmitCIPolicySigning(string metadataPath, + string unsignedCIFilePath, string signedCIFilePath, string timeStamperUrl); + + } +} diff --git a/src/CodeSigning/CodeSigning/Models/Metadata.cs b/src/CodeSigning/CodeSigning/Models/Metadata.cs new file mode 100644 index 000000000000..c16b4df692fd --- /dev/null +++ b/src/CodeSigning/CodeSigning/Models/Metadata.cs @@ -0,0 +1,27 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace Microsoft.Azure.Commands.CodeSigning.Models +{ + public class Metadata + { + public string Endpoint { get; set; } + public string CodeSigningAccountName { get; set; } + public string CertificateProfileName { get; set; } + public List ExcludeCredentials { get; set; } = new List(); + public string AccessToken { get; set; } + } +} diff --git a/src/CodeSigning/CodeSigning/Models/PSSigningCertificate.cs b/src/CodeSigning/CodeSigning/Models/PSSigningCertificate.cs new file mode 100644 index 000000000000..9efd3b5f07fc --- /dev/null +++ b/src/CodeSigning/CodeSigning/Models/PSSigningCertificate.cs @@ -0,0 +1,22 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +namespace Microsoft.Azure.Commands.CodeSigning.Models +{ + internal class PSSigningCertificate + { + public string Thumbprint { get; set; } + public string Subject { get; set; } + } +} \ No newline at end of file diff --git a/src/CodeSigning/CodeSigning/Models/UserSuppliedCredential.cs b/src/CodeSigning/CodeSigning/Models/UserSuppliedCredential.cs new file mode 100644 index 000000000000..b280775bb8bc --- /dev/null +++ b/src/CodeSigning/CodeSigning/Models/UserSuppliedCredential.cs @@ -0,0 +1,41 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using Azure.Core; +using System; +using System.Threading.Tasks; +using System.Threading; + +namespace Microsoft.Azure.Commands.CodeSigning.Models +{ + internal class UserSuppliedCredential : TokenCredential + { + private readonly CodeSigningServiceCredential codeSigningServiceCredential; + + public UserSuppliedCredential(CodeSigningServiceCredential codeSigningServiceCredential) + { + this.codeSigningServiceCredential = codeSigningServiceCredential; + } + + public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + return new AccessToken(codeSigningServiceCredential.GetToken(), DateTimeOffset.UtcNow); + } + + public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + return new ValueTask(this.GetToken(requestContext, cancellationToken)); + } + } +} diff --git a/src/CodeSigning/CodeSigning/Properties/AssemblyInfo.cs b/src/CodeSigning/CodeSigning/Properties/AssemblyInfo.cs new file mode 100644 index 000000000000..7636a12517c9 --- /dev/null +++ b/src/CodeSigning/CodeSigning/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Microsoft Azure Powershell - Code Signing")] +[assembly: AssemblyCompany(Microsoft.WindowsAzure.Commands.Common.AzurePowerShell.AssemblyCompany)] +[assembly: AssemblyProduct(Microsoft.WindowsAzure.Commands.Common.AzurePowerShell.AssemblyProduct)] +[assembly: AssemblyCopyright(Microsoft.WindowsAzure.Commands.Common.AzurePowerShell.AssemblyCopyright)] + +[assembly: ComVisible(false)] +[assembly: CLSCompliant(false)] + +[assembly: Guid("ea901aab-24ca-4004-a198-b9a37a4c8070")] +[assembly: AssemblyVersion("1.0.0")] +[assembly: AssemblyFileVersion("1.0.0")] +#if !SIGN +[assembly: InternalsVisibleTo("Microsoft.Azure.PowerShell.Cmdlets.CodeSigning.Test")] +#endif diff --git a/src/CodeSigning/CodeSigning/Properties/Resources.Designer.cs b/src/CodeSigning/CodeSigning/Properties/Resources.Designer.cs new file mode 100644 index 000000000000..c2a0f1a26017 --- /dev/null +++ b/src/CodeSigning/CodeSigning/Properties/Resources.Designer.cs @@ -0,0 +1,336 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Azure.PowerShell.Cmdlets.CodeSigning.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Azure.PowerShell.Cmdlets.CodeSigning.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The current credentials do not have access to Azure Active Directory. Please either use an ObjectId to refer to ActiveDirectory objects, or log in using credentials that have access to Azure Active Directory.. + /// + internal static string ActiveDirectoryClientNull { + get { + return ResourceManager.GetString("ActiveDirectoryClientNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Please make sure you have sufficient permissions in Microsoft Graph to get and list directory objects for validation to work. Otherwise skip with `-BypassObjectIdValidation`.. + /// + internal static string ADGraphPermissionWarning { + get { + return ResourceManager.GetString("ADGraphPermissionWarning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Email argument specified, '{1}', matches multiple objects in the Azure Active Directory tenant '{2}'. Please use -UserPrincipalName to narrow down the filter to a single object. The TenantID displayed by the cmdlet 'Get-AzContext' is the current subscription's Azure Active Directory.. + /// + internal static string ADObjectAmbiguous { + get { + return ResourceManager.GetString("ADObjectAmbiguous", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Retrieving object ID from Azure Active Directory failed. {0} + /// + ///Please provide object ID for the user or service principal to set a vault access policy. + ///You can find the object ID using Azure Active Directory Module for Windows PowerShell.. + /// + internal static string ADObjectIDRetrievalFailed { + get { + return ResourceManager.GetString("ADObjectIDRetrievalFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot find the Active Directory object '{0}' in tenant '{1}'. Please make sure that the user or application service principal you are authorizing is registered in the current subscription's Azure Active directory. The TenantID displayed by the cmdlet 'Get-AzContext' is the current subscription's Azure Active directory.. + /// + internal static string ADObjectNotFound { + get { + return ResourceManager.GetString("ADObjectNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The 'all' permission will be removed in May 2018 and does not include the 'purge' permission. 'Purge' permission must be explicitly set.. + /// + internal static string AllPermissionExpansionWarning { + get { + return ResourceManager.GetString("AllPermissionExpansionWarning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find application by '{0}'.. + /// + internal static string ApplicationNotFoundBy { + get { + return ResourceManager.GetString("ApplicationNotFoundBy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No account found in the context. Please login using Connect-AzAccount.. + /// + internal static string ArmAccountNotFound { + get { + return ResourceManager.GetString("ArmAccountNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Assign role '{0}' to principal '{1}' at scope '{2}'.. + /// + internal static string AssignRole { + get { + return ResourceManager.GetString("AssignRole", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Bad Parameter Set Name. + /// + internal static string BadParameterSetName { + get { + return ResourceManager.GetString("BadParameterSetName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Fetch default CVM Policy failed, {0}. + /// + internal static string FetchDefaultCVMPolicyFailedWithErrorMessage { + get { + return ResourceManager.GetString("FetchDefaultCVMPolicyFailedWithErrorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Fetching default CVM policy from remote failed because {0}. Trying to fetch default CVM policy from local backup copy.. + /// + internal static string FetchDefaultCVMPolicyFromLocal { + get { + return ResourceManager.GetString("FetchDefaultCVMPolicyFromLocal", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Can not find file '{0}'.. + /// + internal static string FileNotFound { + get { + return ResourceManager.GetString("FileNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Overwrite File ?. + /// + internal static string FileOverwriteCaption { + get { + return ResourceManager.GetString("FileOverwriteCaption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Overwrite existing file at '{0}' ?. + /// + internal static string FileOverwriteMessage { + get { + return ResourceManager.GetString("FileOverwriteMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid application Id.. + /// + internal static string InvalidApplicationId { + get { + return ResourceManager.GetString("InvalidApplicationId", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid AzureEnvironment.. + /// + internal static string InvalidAzureEnvironment { + get { + return ResourceManager.GetString("InvalidAzureEnvironment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No current subscription has been designated. Use Set-AzContext -SubscriptionName <subscriptionName> to set the current subscription.. + /// + internal static string InvalidCurrentSubscription { + get { + return ResourceManager.GetString("InvalidCurrentSubscription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot parse ObjectId into Guid.. + /// + internal static string InvalidObjectIdSyntax { + get { + return ResourceManager.GetString("InvalidObjectIdSyntax", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No subscription is currently selected. Use Set-AzContext to activate a subscription.. + /// + internal static string InvalidSelectedSubscription { + get { + return ResourceManager.GetString("InvalidSelectedSubscription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Your Azure credentials have not been set up or have expired, please run Connect-AzAccount to set up your Azure credentials.. + /// + internal static string InvalidSubscriptionState { + get { + return ResourceManager.GetString("InvalidSubscriptionState", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid tag format. Expect @{Name = "tagName"} or @{Name = "tagName"; Value = "tagValue"}. + /// + internal static string InvalidTagFormat { + get { + return ResourceManager.GetString("InvalidTagFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid vault name.. + /// + internal static string InvalidVaultName { + get { + return ResourceManager.GetString("InvalidVaultName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid vault uri '{0}'. Vault uri must contain valid dns host name with domain suffix '{1}'.. + /// + internal static string InvalidVaultUri { + get { + return ResourceManager.GetString("InvalidVaultUri", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There is no default user account associated with this subscription. Certificate accounts are not supported with Azure Key Vault.. + /// + internal static string NoDefaultUserAccount { + get { + return ResourceManager.GetString("NoDefaultUserAccount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No tenant found in the context. Please ensure that the credentials you provided are authorized to access an Azure subscription, then run Connect-AzAccount to login.. + /// + internal static string NoTenantInContext { + get { + return ResourceManager.GetString("NoTenantInContext", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find the role assignment by given parameters.. + /// + internal static string RoleAssignmentNotFound { + get { + return ResourceManager.GetString("RoleAssignmentNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not to find role definition by '{0}'.. + /// + internal static string RoleDefinitionNotFound { + get { + return ResourceManager.GetString("RoleDefinitionNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The file format of '{0}' is not supported.. + /// + internal static string UnsupportedFileFormat { + get { + return ResourceManager.GetString("UnsupportedFileFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find user by '{0}'.. + /// + internal static string UserNotFoundBy { + get { + return ResourceManager.GetString("UserNotFoundBy", resourceCulture); + } + } + } +} diff --git a/src/CodeSigning/CodeSigning/Properties/Resources.resx b/src/CodeSigning/CodeSigning/Properties/Resources.resx new file mode 100644 index 000000000000..11e68525f381 --- /dev/null +++ b/src/CodeSigning/CodeSigning/Properties/Resources.resx @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Retrieving object ID from Azure Active Directory failed. {0} + +Please provide object ID for the user or service principal to set a vault access policy. +You can find the object ID using Azure Active Directory Module for Windows PowerShell. + + + Cannot find the Active Directory object '{0}' in tenant '{1}'. Please make sure that the user or application service principal you are authorizing is registered in the current subscription's Azure Active directory. The TenantID displayed by the cmdlet 'Get-AzContext' is the current subscription's Azure Active directory. + + + No account found in the context. Please login using Connect-AzAccount. + + + Bad Parameter Set Name + + + Invalid application Id. + + + Cannot parse ObjectId into Guid. + + + Invalid AzureEnvironment. + + + No current subscription has been designated. Use Set-AzContext -SubscriptionName <subscriptionName> to set the current subscription. + + + No subscription is currently selected. Use Set-AzContext to activate a subscription. + + + Your Azure credentials have not been set up or have expired, please run Connect-AzAccount to set up your Azure credentials. + + + Invalid tag format. Expect @{Name = "tagName"} or @{Name = "tagName"; Value = "tagValue"} + + + Invalid vault name. + + + Invalid vault uri '{0}'. Vault uri must contain valid dns host name with domain suffix '{1}'. + + + Can not find file '{0}'. + + + There is no default user account associated with this subscription. Certificate accounts are not supported with Azure Key Vault. + + + No tenant found in the context. Please ensure that the credentials you provided are authorized to access an Azure subscription, then run Connect-AzAccount to login. + + + The file format of '{0}' is not supported. + + + The 'all' permission will be removed in May 2018 and does not include the 'purge' permission. 'Purge' permission must be explicitly set. + + + Overwrite File ? + + + Overwrite existing file at '{0}' ? + + + The Email argument specified, '{1}', matches multiple objects in the Azure Active Directory tenant '{2}'. Please use -UserPrincipalName to narrow down the filter to a single object. The TenantID displayed by the cmdlet 'Get-AzContext' is the current subscription's Azure Active Directory. + + + The current credentials do not have access to Azure Active Directory. Please either use an ObjectId to refer to ActiveDirectory objects, or log in using credentials that have access to Azure Active Directory. + + + Could not find application by '{0}'. + + + Assign role '{0}' to principal '{1}' at scope '{2}'. + + + Could not find the role assignment by given parameters. + + + Could not to find role definition by '{0}'. + + + Could not find user by '{0}'. + + + Please make sure you have sufficient permissions in Microsoft Graph to get and list directory objects for validation to work. Otherwise skip with `-BypassObjectIdValidation`. + + + Fetch default CVM Policy failed, {0} + + + Fetching default CVM policy from remote failed because {0}. Trying to fetch default CVM policy from local backup copy. + + \ No newline at end of file diff --git a/src/CodeSigning/CodeSigning/help/Get-AzCodeSigningCustomerEku.md b/src/CodeSigning/CodeSigning/help/Get-AzCodeSigningCustomerEku.md new file mode 100644 index 000000000000..9107a2819f95 --- /dev/null +++ b/src/CodeSigning/CodeSigning/help/Get-AzCodeSigningCustomerEku.md @@ -0,0 +1,143 @@ +--- +external help file: Microsoft.Azure.PowerShell.Cmdlets.CodeSigning.dll-Help.xml +Module Name: Az.CodeSigning +ms.assetid: 846F781C-73A3-4BBE-ABD9-897371109FBE +online version: https://learn.microsoft.com/powershell/module/az.codesigning/get-azcodesigningcustomereku +schema: 2.0.0 +--- + +# Get-AzCodeSigningCustomerEku + +## SYNOPSIS +Retrieve Azure.CodeSigning customer Eku + +## SYNTAX + +### InteractiveSubmit (Default) +``` +Get-AzCodeSigningCustomerEku [-AccountName] [-ProfileName] -EndpointUrl -MetadataFilePath +``` + + +## DESCRIPTION +The **Get-AzCodeSigningCustomerEku ** cmdlet retrieves customer Eku. +Use this cmdlet to retrieve customer Eku. +There are two sets of parameters. One set uses AccountName, ProfileName, and EndpointUrl. +Another set uses MetadataFilePath. + +## EXAMPLES + +### Example 1: Retrieve customer Eku by account and profile name +```powershell +Get-AzCodeSigningCustomerEku -AccountName 'contoso' -ProfileName 'contososigning' -EndpointUrl 'https://wus.codesigning.azure.net' +``` + +```output +1.3.6.1.5.5.7.3.0 +``` + +This command retrieves the customer eku by account and profile name. + +### Example 2: Retrieve customer Eku by metadata file path + +```powershell +Get-AzCodeSigningCustomerEku -MetadataFilePath 'c:\cisigning\metadata_input.json' +``` + +```output +1.3.6.1.5.5.7.3.0 +``` + +This command retrieves the customer eku by the metadata file configuration. + +## PARAMETERS + +### -AccountName +Specifies Azure CodeSigning AccountName used to sign CI policy. + +```yaml +Type: System.String +Parameter Sets: ByAccountProfileNameParameterSet + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProfileName +Specifies Azure CodeSigning ProfileName used to sign CI policy. + +```yaml +Type: System.String +Parameter Sets: ByAccountProfileNameParameterSet + +Required: True +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -EndpointUrl +Specifies Azure CodeSigning Endpoint used to sign CI policy. It's an Url, format is https://xxx.codesigning.azure.net + +```yaml +Type: System.String +Parameter Sets: ByAccountProfileNameParameterSet, ByMetadataFileParameterSet + +Required: True +Position: 3 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -MetadataFilePath +Specifies Azure CodeSigning Metadata file path used to sign CI policy. It's a file path, and the metadata content is below. File content example: +{ + "Endpoint": "https://xxx.codesigning.azure.net/", + "CodeSigningAccountName": "acstest", + "CertificateProfileName": "acstestCert1" +} + +```yaml +Type: System.String +Parameter Sets: ByMetadataFileParameterSet + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### Azure CodeSigning AccountName + +### Azure CodeSigning Profile Name + +### Azure CodeSigning Signing EndpointUrl + +### Azure CodeSigning UnSigned CI Policy File Path + +### Azure CodeSigning Signed CI Policy File Path Destination + +### System.String + +## OUTPUTS + +### Signed CI Policy file + +## NOTES + +## RELATED LINKS + +[Get-AzCodeSigningCustomerEku](./Get-AzCodeSigningCustomerEku.md) + +[Invoke-AzCodeSigningCIPolicySigning](./Invoke-AzCodeSigningCIPolicySigning.md) diff --git a/src/CodeSigning/CodeSigning/help/Get-AzCodeSigningRootCert.md b/src/CodeSigning/CodeSigning/help/Get-AzCodeSigningRootCert.md new file mode 100644 index 000000000000..092ea8d49980 --- /dev/null +++ b/src/CodeSigning/CodeSigning/help/Get-AzCodeSigningRootCert.md @@ -0,0 +1,162 @@ +--- +external help file: Microsoft.Azure.PowerShell.Cmdlets.CodeSigning.dll-Help.xml +Module Name: Az.CodeSigning +ms.assetid: 846F781C-73A3-4BBE-ABD9-897371109FBE +online version: https://learn.microsoft.com/powershell/module/az.codesigning/get-azcodesigningrootcert +schema: 2.0.0 +--- + +# Get-AzCodeSigningRootCert + +## SYNOPSIS +Retrieve Azure.CodeSigning Root Cert + +## SYNTAX + +### InteractiveSubmit (Default) +``` +Get-AzCodeSigningRootCert [-AccountName] [-ProfileName] -EndpointUrl +-MetadataFilePath +``` + + +## DESCRIPTION +The **Get-AzCodeSigningRootCert** cmdlet retrieves Azure CodeSigning Root Cert. +Use this cmdlet to retrieve Azure CodeSigning Root Cert. +There are two sets of parameters. One set uses AccountName, ProfileName, and EndpointUrl. +Another set uses MetadataFilePath. +Destination is the downloaded root cert file path, which incldues the file name and externsion .cer. +## EXAMPLES + +### Example 1: Retrieve a root cert by account and profile name +```powershell +Get-AzCodeSigningRootCert -AccountName 'contoso' -ProfileName 'contososigning' -EndpointUrl 'https://wus.codesigning.azure.net' -Destination 'c:\acs\rootcert.cer' +``` + +```output +Thumbprint Subject +---------- ------- +3A7B1F8C2E9D5A0B4F6E2C1D9F4B8A3E CN=Microsoft Identity Verification Root Certificate Authority 2020, O=Microsoft +``` + +This command retrieves a root certificate that is currently in use for signing by the account and profile. + +### Example 2: Retrieve a root cert using the metadata file path configuration + +```powershell +Get-AzCodeSigningRootCert -MetadataFilePath 'c:\cisigning\metadata_input.json' -Destination 'c:\acs\rootcert.cer' +``` + +```output +Thumbprint Subject +---------- ------- +3A7B1F8C2E9D5A0B4F6E2C1D9F4B8A3E CN=Microsoft Identity Verification Root Certificate Authority 2020, O=Microsoft +``` + +This command retrieves a root certificate that is currently in use for signing by the metadata configuration. + +## PARAMETERS + +### -AccountName +Specifies Azure CodeSigning AccountName used to sign CI policy. + +```yaml +Type: System.String +Parameter Sets: ByAccountProfileNameParameterSet + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProfileName +Specifies Azure CodeSigning ProfileName used to sign CI policy. + +```yaml +Type: System.String +Parameter Sets: ByAccountProfileNameParameterSet + +Required: True +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -EndpointUrl +Specifies Azure CodeSigning Endpoint used to sign CI policy. It's an Url, format is https://xxx.codesigning.azure.net + +```yaml +Type: System.String +Parameter Sets: ByAccountProfileNameParameterSet, ByMetadataFileParameterSet + +Required: True +Position: 3 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -MetadataFilePath +Specifies Azure CodeSigning Metadata file path used to sign CI policy. It's a file path, and the metadata content is below. File content example: +{ + "Endpoint": "https://xxx.codesigning.azure.net/", + "CodeSigningAccountName": "acstest", + "CertificateProfileName": "acstestCert1" +} + +```yaml +Type: System.String +Parameter Sets: ByMetadataFileParameterSet + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Destination +Specifies the downloaed root cert file path. + +```yaml +Type: System.String +Parameter Sets: ByAccountProfileNameParameterSet, ByMetadataFileParameterSet + +Required: True +Position: 5 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### Azure CodeSigning AccountName + +### Azure CodeSigning Profile Name + +### Azure CodeSigning Signing EndpointUrl + +### Azure CodeSigning UnSigned CI Policy File Path + +### Azure CodeSigning Signed CI Policy File Path Destination + +### System.String + +## OUTPUTS + +### Signed CI Policy file + +## NOTES + +## RELATED LINKS + +[Get-AzCodeSigningCustomerEku](./Get-AzCodeSigningCustomerEku.md) + +[Invoke-AzCodeSigningCIPolicySigning](./Invoke-AzCodeSigningCIPolicySigning.md) diff --git a/src/CodeSigning/CodeSigning/help/Invoke-AzCodeSigningCIPolicySigning.md b/src/CodeSigning/CodeSigning/help/Invoke-AzCodeSigningCIPolicySigning.md new file mode 100644 index 000000000000..34d790b4cb50 --- /dev/null +++ b/src/CodeSigning/CodeSigning/help/Invoke-AzCodeSigningCIPolicySigning.md @@ -0,0 +1,193 @@ +--- +external help file: Microsoft.Azure.PowerShell.Cmdlets.CodeSigning.dll-Help.xml +Module Name: Az.CodeSigning +ms.assetid: 846F781C-73A3-4BBE-ABD9-897371109FBE +online version: https://learn.microsoft.com/powershell/module/az.codesigning/invoke-azcodesigningcipolicysigning +schema: 2.0.0 +--- + +# Invoke-AzCodeSigningCIPolicySigning + +## SYNOPSIS +Invoke CI Policy signing to Azure.CodeSigning + +## SYNTAX + +### InteractiveSubmit (Default) +``` +Invoke-AzCodeSigningCIPolicySigning [-AccountName] [-ProfileName] -EndpointUrl +-MetadataFilePath +-Path -Destination -TimeStamperUrl +``` + + +## DESCRIPTION +The **Invoke-AzCodeSigningCIPolicySigning** cmdlet signs the CI Policy bin file. +Use this cmdlet to sign a CI Policy bin file. +There are two sets of parameters. One set uses AccountName, ProfileName, and EndpointUrl. +Another set uses MetadataFilePath. +Path is the original unsigned CI Policy file path. +Destination is the signing CI Policy file path, which includes file name. +TimeStamperUrl is optional, but it's strongly recommended to do TimeStamping along with Signing. + +## EXAMPLES + +### Example 1: Sign a CI Policy .bin file by account and profile name + +```powershell +Invoke-AzCodeSigningCIPolicySigning -AccountName 'contoso' -ProfileName 'contososigning' -EndpointUrl 'https://wus.codesigning.azure.net' -Path 'c:\cisigning\contosocipolicy.bin' -Destination 'c:\cisigning\signed_contosocipolicy.bin' -TimeStamperUrl 'http://timestamp.acs.microsoft.com' +``` + +```output +CI Policy is successfully signed. c:\cisigning\signed_contosocipolicy.bin +``` + +This command signs a CI policy by account and profile, it also timestamps the signature using the timestamp url provided. + +### Example 2: Sign a CI Policy .bin file by metadata file configuration + +```powershell +Invoke-AzCodeSigningCIPolicySigning -MetadataFilePath 'c:\cisigning\metadata_input.json' -Path 'c:\cisigning\contosocipolicy.bin' -Destination 'c:\cisigning\signed_contosocipolicy.bin' -TimeStamperUrl 'http://timestamp.acs.microsoft.com' +``` + +```output +CI Policy is successfully signed. c:\cisigning\signed_contosocipolicy.bin +``` + +This command signs a CI policy by the metadata configuration, it also timestamps the signature using the timestamp url provided. + +## PARAMETERS + +### -AccountName +Specifies Azure CodeSigning AccountName used to sign CI policy. + +```yaml +Type: System.String +Parameter Sets: ByAccountProfileNameParameterSet + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProfileName +Specifies Azure CodeSigning ProfileName used to sign CI policy. + +```yaml +Type: System.String +Parameter Sets: ByAccountProfileNameParameterSet + +Required: True +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -EndpointUrl +Specifies Azure CodeSigning Endpoint used to sign CI policy. It's an Url, format is https://xxx.codesigning.azure.net + +```yaml +Type: System.String +Parameter Sets: ByAccountProfileNameParameterSet, ByMetadataFileParameterSet + +Required: True +Position: 3 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -MetadataFilePath +Specifies Azure CodeSigning Metadata file path used to sign CI policy. It's a file path, and the metadata content is below. File content example: +{ + "Endpoint": "https://xxx.codesigning.azure.net/", + "CodeSigningAccountName": "acstest", + "CertificateProfileName": "acstestCert1" +} + +```yaml +Type: System.String +Parameter Sets: ByMetadataFileParameterSet + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Path +Specifies the original unsigned CI policy file path. The CI policy file extension is .bin, not xml. + +```yaml +Type: System.String +Parameter Sets: ByAccountProfileNameParameterSet, ByMetadataFileParameterSet + +Required: True +Position: 4 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Destination +Specifies the signed CI policy file path. The signed CI policy file extension is .bin. + +```yaml +Type: System.String +Parameter Sets: ByAccountProfileNameParameterSet, ByMetadataFileParameterSet + +Required: True +Position: 5 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TimeStamperUrl +Specifies Azure CodeSigning TimeStamper Url used to sign CI policy. The format is Url, recommended timestamper is http://timestamp.acs.microsoft.com. + +```yaml +Type: System.String +Parameter Sets: ByAccountProfileNameParameterSet, ByMetadataFileParameterSet + +Required: False +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + + + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### Azure CodeSigning AccountName + +### Azure CodeSigning Profile Name + +### Azure CodeSigning Signing EndpointUrl + +### Azure CodeSigning UnSigned CI Policy File Path + +### Azure CodeSigning Signed CI Policy File Path Destination + +### System.String + +## OUTPUTS + +### Signed CI Policy file + +## NOTES + +## RELATED LINKS + +[Get-AzCodeSigningCustomerEku](./Get-AzCodeSigningCustomerEku.md) + +[Get-AzCodeSigningRootCert](./Get-AzCodeSigningRootCert.md) diff --git a/tools/StaticAnalysis/Exceptions/Az.CodeSigning/SignatureIssues.csv b/tools/StaticAnalysis/Exceptions/Az.CodeSigning/SignatureIssues.csv new file mode 100644 index 000000000000..50d533c76a75 --- /dev/null +++ b/tools/StaticAnalysis/Exceptions/Az.CodeSigning/SignatureIssues.csv @@ -0,0 +1,2 @@ +"Module","ClassName","Target","Severity","ProblemId","Description","Remediation" +"Microsoft.Azure.PowerShell.Cmdlets.CodeSigning.dll","Microsoft.Azure.Commands.CodeSigning.InvokeCIPolicySigning","Invoke-AzCodeSigningCIPolicySigning","1","8100","Invoke-AzCodeSigningCIPolicySigning Does not support ShouldProcess but the cmdlet verb Invoke indicates that it should.","Determine if the cmdlet should implement ShouldProcess and if so determine if it should implement Force / ShouldContinue"