diff --git a/EsrpScan.yml b/EsrpScan.yml index 8e8d828..f745541 100644 --- a/EsrpScan.yml +++ b/EsrpScan.yml @@ -3,6 +3,8 @@ parameters: default: "$(Pipeline.Workspace)" - name: "pattern" default: "*.dll,*.exe" + - name: "scanningService" + default: "pwshEsrpScanning" steps: - task: UseDotNet@2 @@ -10,10 +12,16 @@ steps: inputs: version: 2.x +- pwsh: | + Write-Verbose -Verbose "scanPath = '${{ parameters.scanPath }}'" + Write-Verbose -Verbose "pattern = '${{ parameters.pattern }}'" + Write-Verbose -Verbose "scanningService = '${{ parameters.scanningService }}'" + displayName: Log parameters + - task: SFP.build-tasks.custom-build-task-2.EsrpMalwareScanning@1 displayName: 'Malware Scanning' inputs: - ConnectedServiceName: pwshEsrpScanning + ConnectedServiceName: ${{ parameters.scanningService }} FolderPath: ${{ parameters.scanPath }} Pattern: ${{ parameters.pattern }} UseMinimatch: true diff --git a/EsrpSign.yml b/EsrpSign.yml index 967a579..bd58944 100644 --- a/EsrpSign.yml +++ b/EsrpSign.yml @@ -15,6 +15,12 @@ parameters: default: "auto" - name: "alwaysCopy" default: "False" + - name: "useCustomEsrpJson" + default: "false" + - name: "verifySignature" + default: "false" + - name: "pageHash" + default: "true" steps: - task: UseDotNet@2 @@ -27,6 +33,18 @@ steps: Write-Verbose -Verbose "signOutputPath = '${{ parameters.signOutputPath }}'" Write-Verbose -Verbose "certificateId = '${{ parameters.certificateId }}'" Write-Verbose -Verbose "pattern = '${{ parameters.pattern }}'" + Write-Verbose -Verbose "useMinimatch = '${{ parameters.useMinimatch }}'" + Write-Verbose -Verbose "signingService = '${{ parameters.signingService }}'" + Write-Verbose -Verbose "shouldSign = '${{ parameters.shouldSign }}'" + Write-Verbose -Verbose "alwaysCopy = '${{ parameters.alwaysCopy }}'" + Write-Verbose -Verbose "useCustomEsrpJson = '${{ parameters.useCustomEsrpJson }}'" + Write-Verbose -Verbose "verifySignature = '${{ parameters.verifySignature }}'" + Write-Verbose -Verbose "pageHash = '${{ parameters.pageHash }}'" + + if (('${{ parameters.useCustomEsrpJson }}' -ne 'false') -and ('${{ parameters.certificateId }}' -ne '')) + { + throw "Only one of useCustomEsrpJson and certificateId must be set!" + } displayName: Log parameters - pwsh: | @@ -52,6 +70,8 @@ steps: signOutputPath: ${{ parameters.signOutputPath }} pattern: ${{ parameters.pattern }} certificateId: ${{ parameters.certificateId }} + verifySignature: ${{ parameters.verifySignature }} + pageHash: ${{ parameters.pageHash }} - ${{ if eq(parameters.certificateId , 'CP-231522') }}: - template: template-compliance/authenticode-sign.yml @@ -60,6 +80,8 @@ steps: signOutputPath: ${{ parameters.signOutputPath }} pattern: ${{ parameters.pattern }} certificateId: ${{ parameters.certificateId }} + verifySignature: ${{ parameters.verifySignature }} + pageHash: ${{ parameters.pageHash }} - ${{ if eq(parameters.certificateId, 'CP-401405') }}: - template: template-compliance/nuget-sign.yml @@ -68,6 +90,7 @@ steps: signOutputPath: ${{ parameters.signOutputPath }} pattern: ${{ parameters.pattern }} certificateId: ${{ parameters.certificateId }} + verifySignature: ${{ parameters.verifySignature }} - ${{ if or(eq(parameters.certificateId, 'CP-450779-Pgp'),eq(parameters.certificateId, 'CP-450778-Pgp')) }}: - template: template-compliance/pgp-sign.yml @@ -83,6 +106,11 @@ steps: pattern: ${{ parameters.pattern }} certificateId: ${{ parameters.certificateId }} +- ${{ if eq(parameters.useCustomEsrpJson, 'true') }}: + - template: template-compliance/custom-sign.yml + parameters: + signOutputPath: ${{ parameters.signOutputPath }} + - pwsh: | Write-Verbose -Verbose "EsrpJson = '${env:EsrpJson}'" displayName: Log Json diff --git a/README.md b/README.md index 4ee93b7..3671c57 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,8 @@ Details can be found in the PowerShell Maintainers teams channel's Wiki tab. parameters: # the folder which contains the binaries to sign buildOutputPath: $(signSrcPath) - # the location to put the signed output + # the location to put the signed output. + # Note, All files in "buildOutputPath" are copied to "signOutputPath" not just ones matching the "pattern". signOutputPath: $(signOutPath) # the certificate ID to use certificateId: "CP-230012" @@ -86,6 +87,29 @@ Details can be found in the PowerShell Maintainers teams channel's Wiki tab. # decides if the task should use minimatch for the pattern matching. # https://github.com/isaacs/minimatch#features useMinimatch: false + # If "true", use a custom JSON string for ESRP signing. Defaults to "false". + useCustomEsrpJson: false + # If "true", ESRP will automatically verify your files are signed properly (eg signtool /verify). + # Only supported for authenticode & nuget signing. + # Defaults to "false". + verifySignature: false + # If "true", ESRP will page hash sign your files. + # Only supported for authenticode signing. + # Defaults to "true". + pageHash: true + # If "true", ESRP will be called to sign your files. + # If "false", ESRP is not called, files will not be signed. + # If "auto", ESRP is called if and only certain build conditions are met. + # Defaults to "auto". + shouldSign: 'auto' + # If "true", your files are always copied to signOutputPath even if ESRP is not called to sign the files. + # Useful to set to true if you want your build pipeline to behave as close to 'normal' as possible when testing and not signing. + # Defaults to "false". + alwaysCopy: false + # The name of the Azure DevOps Service connection you configured for ESRP Signing. + # Defaults to "pwshSigning". + signingService: 'pwshSigning' + ``` ### ESRP Authenticode minimatch example @@ -158,6 +182,76 @@ This example signs `pkg` files recursively, using minimatch. **\*.pkg useMinimatch: true ``` + +### ESRP custom signing JSON example +1. Set the build variable ESRP_TEMPLATE_CUSTOM_JSON to your desired ESRP JSON string. +2. Call EsrpSign.yml@ComplianceRepo with certificateId: "" and useCustomEsrpJson: true. + +```yaml +- pwsh: | + [string] $SigningServer = '$(SigningServer)' + Write-Verbose "SigningServer - $SigningServer" -Verbose + + $esrpParameters = @{ + OpusName = "Microsoft" + OpusInfo = "http://www.microsoft.com" + FileDigest = "/fd sha256" + TimeStamp = "/tr ""$SigningServer"" /td sha256" + } + + $esrp = @( + @{ + KeyCode = "Dynamic" + CertTemplateName = "WINMSAPP1ST" + CertSubjectName = "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" + operationCode = "SigntoolSign" + Parameters = $esrpParameters + ToolName = "sign" + ToolVersion = "1.0"}, + @{ + KeyCode = "Dynamic" + CertTemplateName = "WINMSAPP1ST" + CertSubjectName = "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" + OperationCode = "SigntoolVerify" + ToolName = "sign" + ToolVersion = "1.0" + } + ) + + $vstsCommandString = "vso[task.setvariable variable=ESRP_TEMPLATE_CUSTOM_JSON][$($esrp | ConvertTo-Json -Compress)]" + Write-Verbose -Message ("sending " + $vstsCommandString) -Verbose + Write-Host "##$vstsCommandString" + displayName: Generate app signing JSON + +- template: EsrpSign.yml@ComplianceRepo + parameters: + buildOutputPath: $(signSrcPath) + signOutputPath: $(signOutPath) + # Explicitly set to "" for custom string + certificateId: "" + pattern: '*.msix' + useMinimatch: false + # Use ESRP_TEMPLATE_CUSTOM_JSON as custom string + useCustomEsrpJson: true +``` + +### ESRP Custom Signing Service Connection Example + +This example uses a custom signing (Azure DevOps) service connection name. + +```yaml + - template: EsrpSign.yml@ComplianceRepo + parameters: + buildOutputPath: $(signSrcPath) + signOutputPath: $(signOutPath) + certificateId: "CP-230012" + pattern: '*.dll' + # The name of the Azure DevOps Service connection you configured for ESRP Signing. + # Defaults to "pwshSigning". + signingService: 'FactoryOrchestratorSigning' + +``` + ## ESRP Malware Scanning Template Overview ** Requires on-boarding, see the wiki in the internal PowerShell Maintainers teams channel ** @@ -186,4 +280,23 @@ scanning on each upload will allow us to detect when any malware was introduced. **\*.rpm **\*.deb **\*.tar.gz + # The name of the Azure DevOps Service connection you configured for ESRP Malware Scanning. + # Defaults to "pwshEsrpScanning". + scanningService: 'pwshEsrpScanning' ``` + +### ESRP Scanning Custom Service Example + +This example uses a custom ESRP malware scanning (Azure DevOps) service name. + +```yaml + - template: EsrpSign.yml@ComplianceRepo + parameters: + buildOutputPath: $(signSrcPath) + signOutputPath: $(signOutPath) + certificateId: "CP-230012" + pattern: | + **\*.dll + scanningService: 'FactoryOrchestratorScanning' + +``` \ No newline at end of file diff --git a/template-compliance/authenticode-sign.yml b/template-compliance/authenticode-sign.yml index b4beb21..80917a9 100644 --- a/template-compliance/authenticode-sign.yml +++ b/template-compliance/authenticode-sign.yml @@ -7,6 +7,10 @@ parameters: default: "*.dll,*.exe" - name: "certificateId" default: "CP-230012" + - name: "verifySignature" + default: "false" + - name: "pageHash" + default: "true" steps: - pwsh: | @@ -18,38 +22,43 @@ steps: [string] $SigningServer = '$(SigningServer)' Write-Verbose "SigningServer - $SigningServer" -Verbose - $esrpParameters = @( - @{ - ParameterName = "OpusName" - ParameterValue = "Microsoft" + $esrpParameters = @{ + OpusName = "Microsoft" + OpusInfo = "http://www.microsoft.com" + FileDigest = "/fd sha256" + TimeStamp = "/tr ""$SigningServer"" /td sha256" } - @{ - ParameterName = "OpusInfo" - ParameterValue = "http://www.microsoft.com" - } - @{ - ParameterName = "PageHash" - ParameterValue = "/NPH" - } - @{ - ParameterName = "FileDigest" - ParameterValue = "/fd sha256" - } - @{ - ParameterName = "TimeStamp" - ParameterValue = "/tr ""$SigningServer"" /td sha256" + + if ("${{ parameters.pageHash }}" -eq "true") + { + $esrpParameters.Add("PageHash", "/NPH") } - ) $esrp = @(@{ - keyCode = $CertificateId - operationSetCode = "SigntoolSign" - parameters = $esrpParameters - toolName = "signtool.exe" - toolVersion = "6.2.9304.0" + keyCode = $CertificateId + operationCode = "SigntoolSign" + parameters = $esrpParameters + toolName = "signtool.exe" + toolVersion = "6.2.9304.0" }) - $vstsCommandString = "vso[task.setvariable variable=$VariableName][$($esrp | ConvertTo-Json -Compress)]" + if ("${{ parameters.verifySignature }}" -eq "true") + { + $esrp += @{ + keyCode = $CertificateId + operationCode = "SigntoolVerify" + toolName = "signtool.exe" + toolVersion = "6.2.9304.0" + } + } + + $jsonString = $($esrp | ConvertTo-Json -Depth 99 -Compress) + if ($esrp.Count -eq 1) + { + $jsonString = "[$jsonString]" + } + + $vstsCommandString = "vso[task.setvariable variable=$VariableName]$jsonString" Write-Verbose -Message ("sending " + $vstsCommandString) -Verbose Write-Host "##$vstsCommandString" diff --git a/template-compliance/custom-sign.yml b/template-compliance/custom-sign.yml new file mode 100644 index 0000000..8134535 --- /dev/null +++ b/template-compliance/custom-sign.yml @@ -0,0 +1,21 @@ +parameters: + - name: "signOutputPath" + default: "$(Build.ArtifactStagingDirectory)\\signed" + +steps: +- pwsh: | + if ($null -eq $env:ESRP_TEMPLATE_CUSTOM_JSON) + { + Write-Error "ESRP_TEMPLATE_CUSTOM_JSON variable must be set!" + } + + [string] $VariableName = "EsrpJson" + $vstsCommandString = "vso[task.setvariable variable=$VariableName]$env:ESRP_TEMPLATE_CUSTOM_JSON" + Write-Verbose -Message ("sending " + $vstsCommandString) -Verbose + Write-Host "##$vstsCommandString" + + $vstsCommandString = "vso[task.setvariable variable=GDN_CODESIGN_TARGETDIRECTORY]${{ parameters.signOutputPath }}" + Write-Verbose -Message ("sending " + $vstsCommandString) -Verbose + Write-Host "##$vstsCommandString" + displayName: Generate custom signing JSON + condition: and(succeeded(), eq(variables['ESRP_TEMPLATE_SHOULD_SIGN'], 'True')) diff --git a/template-compliance/nuget-sign.yml b/template-compliance/nuget-sign.yml index 86b4dc9..82bb337 100644 --- a/template-compliance/nuget-sign.yml +++ b/template-compliance/nuget-sign.yml @@ -7,6 +7,8 @@ parameters: default: "*.nupkg" - name: "certificateId" default: "CP-401405" + - name: "verifySignature" + default: "false" steps: - pwsh: | @@ -15,16 +17,30 @@ steps: [string] $VariableName = "EsrpJson" - $esrp = @( - @{ - keyCode = $CertificateId - operationSetCode = "NuGetSign" - toolName = "sign" - toolVersion = "1.0" - } - ) + $esrp = @(@{ + keyCode = $CertificateId + operationSetCode = "NuGetSign" + toolName = "sign" + toolVersion = "1.0" + }) - $vstsCommandString = "vso[task.setvariable variable=$VariableName][$($esrp | ConvertTo-Json -Compress)]" + if ("${{ parameters.verifySignature }}" -eq "true") + { + $esrp += @{ + keyCode = $CertificateId + operationSetCode = "NuGetVerify" + toolName = "sign" + toolVersion = "1.0" + } + } + + $jsonString = $($esrp | ConvertTo-Json -Depth 99 -Compress) + if ($esrp.Count -eq 1) + { + $jsonString = "[$jsonString]" + } + + $vstsCommandString = "vso[task.setvariable variable=$VariableName]$jsonString" Write-Verbose -Message ("sending " + $vstsCommandString) -Verbose Write-Host "##$vstsCommandString"