PoshCode Logo PowerShell Code Repository

Get/Set Signature (CTP3) by Joel Bennett 3 years ago (modification of post by Joel Bennett view diff)
View followups from Joel Bennett | diff | embed code: <script type="text/javascript" src="http://PoshCode.org/embed/754"></script>download | new post

Authenticode 1.6 for CTP3: Now with integrated help. NOTE: The default cert should be put in a ModuleInfo file, but it will work without that, and will prompt you for a certificate (and/or the password for it) only once per session.

Description: Wrappers for the Get-AuthenticodeSignature and Set-AuthenticodeSignature which properly parse paths and don’t kill your pipeline and script when you hit a folder by accident…

  1. #Requires -version 2.0
  2. ## Authenticode.psm1 updated for CTP 3
  3. ####################################################################################################
  4. ## Wrappers for the Get-AuthenticodeSignature and Set-AuthenticodeSignature cmdlets
  5. ## These properly parse paths, so they don't kill your pipeline and script if you include a folder
  6. ##
  7. ## Usage:
  8. ## ls | Get-AuthenticodeSignature
  9. ##    Get all the signatures
  10. ##
  11. ## ls | Select-Signed -Mine -Broken | Set-AuthenticodeSignature
  12. ##    Re-sign anything you signed before that has changed
  13. ##
  14. ## ls | Select-Signed -NotMine -TrustedOnly | Set-AuthenticodeSignature
  15. ##    Re-sign scripts that are hash-correct but not signed by me or by someone trusted
  16. ##
  17. ####################################################################################################
  18. ## History:
  19. ## 1.6 - Converted to work with CTP 3, and added function help comments
  20. ## 1.5 - Moved the default certificate setting into the module info Authenticode.psd1 file
  21. ##       Note: If you get this off PoshCode, you'll have to create it yourself, see below:
  22. ## 1.4 - Moved the default certificate setting into an external psd1 file.
  23. ## 1.3 - Fixed some bugs in If-Signed and renamed it to Select-Signed
  24. ##     - Added -MineOnly and -NotMineOnly switches to Select-Signed
  25. ## 1.2 - Added a hack workaround to make it appear as though we can sign and check PSM1 files
  26. ##       It's important to remember that the signatures are NOT checked by PowerShell yet...
  27. ## 1.1 - Added a filter "If-Signed" that can be used like: ls | If-Signed
  28. ##     - With optional switches: ValidOnly, InvalidOnly, BrokenOnly, TrustedOnly, UnsignedOnly
  29. ##     - commented out the default Certificate which won't work for "you"
  30. ## 1.0 - first working version, includes wrappers for Get and Set
  31. ##
  32. ####################################################################################################
  33. ## README! README! README! README! #################################################################
  34. ## README! README! README! README! #################################################################
  35. ##
  36. ## You should set the location to your default signing certificate. The permanent way to do that is
  37. ## to modify (or create) the .psd1 file to set the PrivateData member variable. Otherwise you'll be
  38. ## prompted to provide a cert path whenever you try to sign a script without passing a certificate.
  39. ##
  40. ## The PrivateData variable should point at your code-signing certificate either with a full path
  41. ## or with the THUMBPRINT of a certificate you have available in your Cert:\CurrentUser\My\ provider
  42. ##
  43. ## EG:
  44. ##      F05F583BB5EA4C90E3B9BF1BDD0B657701245BD5
  45. ## OR:
  46. ##      "Cert:\CurrentUser\My\F05F583BB5EA4C90E3B9BF1BDD0B657701245BD5"
  47. ## OR a file name:
  48. ##      "C:\Users\Joel\Documents\WindowsPowerShell\PoshCerts\Joel-Bennett_Code-Signing.pfx"
  49. ##
  50. ## The simplest thing is to just create a new PSD1
  51. ##
  52. ##    New-ModuleManifest .\Authenticode.psd1 -Nested .\Authenticode.psm1 `
  53. ##    -Author "" -COmpany "" -Copy "" -Desc "" `
  54. ##    -Types @() -Formats @() -RequiredMod @() -RequiredAs @() -Other @() `
  55. ##    -PrivateData F05F583BB5EA4C90E3B9BF1BDD0B657701245BD5
  56. ##
  57. ####################################################################################################
  58.  
  59. ####################################################################################################
  60. function Get-UserCertificate {
  61. <#.SYNOPSIS
  62.  Gets the user's default signing certificate so we don't have to ask them over and over...
  63. .DESCRIPTION
  64.  The Get-UserCertificate function retrieves and returns a certificate from the user. It also stores
  65.  the certificate so it can bereused without re-querying for the location and/or password ...
  66. .RETURNVALUE
  67.  An X509Certificate2 suitable for code-signing
  68. ###################################################################################################>
  69. [CmdletBinding()]
  70. PARAM ()
  71. PROCESS {
  72.    trap {
  73.       Write-Host "The authenticode script module requires configuration to function fully!"
  74.       Write-Host
  75.       Write-Host "You must put the path to your default signing certificate in the module metadata"`
  76.                  "file before you can use the module's Set-Authenticode cmdlet or to the 'mine'"`
  77.                  "feature of the Select-Signed or Test-Signature. To set it up, you can do this:"
  78.       Write-Host
  79.       Write-Host "PrivateData = 'C:\Users\${Env:UserName}\Documents\WindowsPowerShell\PoshCerts\Code-Signing.pfx'"
  80.       Write-Host
  81.       Write-Host "If you load your certificate into your 'CurrentUser\My' store, or put the .pfx file"`
  82.                  "into the folder with the Authenthenticode module script, you can just specify it's"`
  83.                  "thumprint or filename, respectively. Otherwise, it should be a full path."
  84.       Write-Error $_
  85.       return      
  86.    }
  87.    # Import-LocalizedData -bindingVariable CertificatePath -EA "STOP"
  88.    if(!$ExecutionContext.SessionState.Module.PrivateData) {
  89.       $certs = ls cert:\CurrentUser\My
  90.       if($certs.Count) {
  91.          Write-Host "You have ${certs.Count} certs in your local cert storage which you can specify by Thumbprint:" -fore cyan
  92.          $certs | Out-Host
  93.       }
  94.       $ExecutionContext.SessionState.Module.PrivateData = $(Read-Host "Please specify a user certificate")
  95.    }
  96.    if($ExecutionContext.SessionState.Module.PrivateData -isnot [System.Security.Cryptography.X509Certificates.X509Certificate2]) {
  97.       # Write-Host "CertificatePath: $ExecutionContext.SessionState.Module.PrivateData" -fore cyan
  98.       $ResolvedPath = $Null
  99.       $ResolvedPath = Resolve-Path $ExecutionContext.SessionState.Module.PrivateData -ErrorAction "SilentlyContinue"
  100.       if(!$ResolvedPath -or !(Test-Path $ResolvedPath -ErrorAction "SilentlyContinue")) {
  101.          # Write-Host "ResolvedPath: $ResolvedPath" -fore green
  102.          $ResolvedPath = Resolve-Path (Join-Path $PsScriptRoot $ExecutionContext.SessionState.Module.PrivateData -ErrorAction "SilentlyContinue") -ErrorAction "SilentlyContinue"
  103.       }
  104.       if(!$ResolvedPath -or !(Test-Path $ResolvedPath -ErrorAction "SilentlyContinue")) {
  105.          # Write-Host "ResolvedPath: $ResolvedPath" -fore yellow
  106.          $ResolvedPath = Resolve-Path (Join-Path "Cert:\CurrentUser\My" $ExecutionContext.SessionState.Module.PrivateData -ErrorAction "SilentlyContinue") -ErrorAction "SilentlyContinue"
  107.       }
  108.  
  109.       $Certificate = get-item $ResolvedPath -ErrorAction "SilentlyContinue"
  110.       if($Certificate -is [System.IO.FileInfo]) {
  111.          $Certificate = Get-PfxCertificate $Certificate -ErrorAction "SilentlyContinue"
  112.       }
  113.       Write-Verbose "Certificate: $($Certificate | Out-String)"
  114.       $ExecutionContext.SessionState.Module.PrivateData = $Certificate
  115.    }
  116.    return $ExecutionContext.SessionState.Module.PrivateData
  117. }
  118. }
  119.  
  120. ####################################################################################################
  121. function Test-Signature {
  122. <#.SYNOPSIS
  123.  Tests a script signature to see if it is valid, or at least unaltered.
  124. .DESCRIPTION
  125.  The Test-Signature function processes the output of Get-AuthenticodeSignature to determine if it
  126.  is Valid, OR **unaltered** and signed by the user's certificate
  127. .NOTES
  128.  Test-Signature returns TRUE even if the root CA certificate can't be verified, as long as the
  129.  signing certificate's thumbnail matches the one specified by Get-UserCertificate.
  130. .EXAMPLE
  131.    ls *.ps1 | Get-AuthenticodeSignature | Where {Test-Signature $_}
  132.  To get the signature reports for all the scripts that we consider safely signed.
  133. .EXAMPLE
  134.    ls | ? { gas $_ | test-signature }
  135.  List all the valid signed scripts (or scripts signed by our cert)
  136. .INPUTTYPE
  137.   System.Management.Automation.Signature
  138. .PARAMETER Signature
  139.   Specifies the signature object to test. This should be the output of Get-AuthenticodeSignature.
  140. .PARAMETER ForceValid
  141.   Switch parameter, forces the signature to be valid (otherwise, even if the certificate chain can't be verified, we will accept the cert which matches the "user" certificate (see Get-UserCertificate).
  142.   Aliases                      Valid
  143. .RETURNVALUE
  144.    Boolean value representing whether the script's signature is valid, or YOUR certificate
  145. ###################################################################################################>
  146. [CmdletBinding()]
  147. PARAM (
  148.    [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
  149. #  The signature to test.
  150.    $Signature
  151. ,
  152.    [Alias("Valid")]
  153.    [Switch]$ForceValid
  154. )
  155.  
  156. return ( $Signature.Status -eq "Valid" -or
  157.       ( !$ForceValid -and
  158.          ($Signature.Status -eq "UnknownError") -and
  159.          ($_.SignerCertificate.Thumbprint -eq $(Get-UserCertificate).Thumbprint)
  160.       ) )
  161. }
  162.  
  163.  
  164.  
  165. ####################################################################################################
  166. function Set-AuthenticodeSignature {
  167. <#.SYNOPSIS
  168.    Adds an Authenticode signature to a Windows PowerShell script or other file.
  169. .DESCRIPTION
  170.    The Set-AuthenticodeSignature function adds an Authenticode signature to any file that supports Subject Interface Package (SIP).
  171.  
  172.    In a Windows PowerShell script file, the signature takes the form of a block of text that indicates the end of the instructions that are executed in the script. If there is a signature  in the file when this cmdlet runs, that signature is removed.
  173. .NOTES
  174.    After the certificate has been validated, but before a signature is added to the file, the function checks the value of the $SigningApproved preference variable. If this variable is not set, or has a value other than TRUE, you are prompted to confirm the signing of the script.
  175.  
  176.    When specifying multiple values for a parameter, use commas to separate the values. For example, "<parameter-name> <value1>, <value2>".
  177. .EXAMPLE
  178.    ls *.ps1 | Set-AuthenticodeSignature -Certificate $Certificate
  179.    
  180.    To sign all of the files with the specified certificate
  181. .EXAMPLE
  182.    ls *.ps1,*.psm1,*.psd1 | Get-AuthenticodeSignature | Where {!(Test-Signature $_ -Valid)} | gci | Set-AuthenticodeSignature
  183.  
  184.    List all the script files, and get and test their signatures, and then sign all of the ones that are not valid, using the user's default certificate.
  185. .INPUTTYPE
  186.    String. You can pipe a file path to Set-AuthenticodeSignature.
  187. .PARAMETER FilePath
  188.    Specifies the path to a file that is being signed.
  189.    Aliases                      Path, FullName
  190. .PARAMETER Certificate
  191.    Specifies the certificate that will be used to sign the script or file. Enter a variable that stores an object representing the certificate or an expression that gets the certificate.
  192.  
  193.    To find a certificate, use Get-PfxCertificate or use the Get-ChildItem cmdlet in the Certificate (Cert:) drive. If the certificate is not valid or does not have code-signing authority, the command fails.
  194. .RETURNVALUE
  195.    System.Management.Automation.Signature
  196. ###################################################################################################>
  197. [CmdletBinding()]
  198. PARAM (
  199.    [Parameter(Position=1, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
  200.    [Alias("FullName","Path")]
  201.    [ValidateScript({
  202.       if((resolve-path $_).Provider.Name -ne "FileSystem") {
  203.          throw "Specified Path is not in the FileSystem: '$_'"
  204.       }
  205.       if(!(Test-Path -PathType Leaf $_)) {
  206.          throw "Specified Path is not a File: '$_'"
  207.       }
  208.       return $true
  209.    })]
  210.    [string[]]
  211.    $FilePath
  212. ,  
  213.    [Parameter(Position=2, Mandatory=$false)]
  214.    $Certificate = $(Get-UserCertificate)
  215. )
  216.  
  217. PROCESS {
  218.    Write-Verbose "Set Authenticode Signature on $FilePath with $($Certificate | Out-String)"
  219.    Microsoft.PowerShell.Security\Set-AuthenticodeSignature -Certificate $Certificate -FilePath $FilePath  
  220. }
  221. }
  222. New-Alias sign Set-AuthenticodeSignature -EA "SilentlyContinue"
  223.  
  224. ####################################################################################################
  225. function Get-AuthenticodeSignature {
  226. <#.SYNOPSIS
  227.    Gets information about the Authenticode signature in a file.
  228. .DESCRIPTION
  229.    The Get-AuthenticodeSignature function gets information about the Authenticode signature in a file. If the file is not signed, the information is retrieved, but the fields are blank.
  230. .NOTES
  231.    For information about Authenticode signatures in Windows PowerShell, type "get-help About_Signing".
  232.  
  233.    When specifying multiple values for a parameter, use commas to separate the values. For example, "-<parameter-name> <value1>, <value2>".
  234. .EXAMPLE
  235.    Get-AuthenticodeSignature script.ps1
  236.    
  237.    To get the signature information about the script.ps1 script file.
  238. .EXAMPLE
  239.    ls *.ps1,*.psm1,*.psd1 | Get-AuthenticodeSignature
  240.    
  241.    Get the signature information for all the script and data files
  242. .EXAMPLE
  243.    ls *.ps1,*.psm1,*.psd1 | Get-AuthenticodeSignature | Where {!(Test-Signature $_ -Valid)} | gci | Set-AuthenticodeSignature
  244.  
  245.    This command gets information about the Authenticode signature in all of the script and module files, and tests the signatures, then signs all of the ones that are not valid.
  246. .INPUTTYPE
  247.    String. You can pipe the path to a file to Get-AuthenticodeSignature.
  248. .PARAMETER FilePath
  249.    The path to the file being examined. Wildcards are permitted, but they must lead to a single file. The parameter name ("-FilePath") is optional.
  250.    Aliases                      Path, FullName
  251. .RETURNVALUE
  252.    System.Management.Automation.Signature
  253. ###################################################################################################>
  254. [CmdletBinding()]
  255. PARAM (
  256.    [Parameter(Position=1, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
  257.    [Alias("FullName","Path")]
  258.    [ValidateScript({
  259.       if((resolve-path $_).Provider.Name -ne "FileSystem") {
  260.          throw "Specified Path is not in the FileSystem: '$_'"
  261.       }
  262.       if(!(Test-Path -PathType Leaf $_)) {
  263.          throw "Specified Path is not a File: '$_'"
  264.       }
  265.       return $true
  266.    })]
  267.    [string[]]
  268.    $FilePath
  269. )
  270.  
  271. PROCESS {
  272.    Microsoft.PowerShell.Security\Get-AuthenticodeSignature -FilePath $FilePath
  273. }
  274. }
  275.  
  276. ####################################################################################################
  277. function Select-Signed {
  278. <#.SYNOPSIS
  279.    Select files based on the status of their Authenticode Signature.
  280. .DESCRIPTION
  281.    The Select-Signed function filters files on the pipeline based on the state of their authenticode signature.
  282. .NOTES
  283.    For information about Authenticode signatures in Windows PowerShell, type "get-help About_Signing".
  284.  
  285.    When specifying multiple values for a parameter, use commas to separate the values. For example, "-<parameter-name> <value1>, <value2>".
  286. .EXAMPLE
  287.    ls *.ps1,*.ps[dm]1 | Select-Signed
  288.    
  289.    To get the signature information about the script.ps1 script file.
  290. .EXAMPLE
  291.    ls *.ps1,*.psm1,*.psd1 | Get-AuthenticodeSignature
  292.    
  293.    Get the signature information for all the script and data files
  294. .EXAMPLE
  295.    ls *.ps1,*.psm1,*.psd1 | Get-AuthenticodeSignature | Where {!(Test-Signature $_ -Valid)} | gci | Set-AuthenticodeSignature
  296.  
  297.    This command gets information about the Authenticode signature in all of the script and module files, and tests the signatures, then signs all of the ones that are not valid.
  298. .INPUTTYPE
  299.    String. You can pipe the path to a file to Get-AuthenticodeSignature.
  300. .PARAMETER FilePath
  301.    The path to the file being examined. Wildcards are permitted, but they must lead to a single file. The parameter name ("-FilePath") is optional.
  302.    Aliases                      Path, FullName
  303. .RETURNVALUE
  304.    System.Management.Automation.Signature
  305. ###################################################################################################>
  306. [CmdletBinding()]
  307. PARAM (
  308.    [Parameter(Position=1, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
  309.    [Alias("FullName")]
  310.    [ValidateScript({
  311.       if((resolve-path $_).Provider.Name -ne "FileSystem") {
  312.          throw "Specified Path is not in the FileSystem: '$_'"
  313.       }
  314.       return $true
  315.    })]
  316.    [string[]]
  317.    $FilePath
  318. ,
  319.    [Parameter()]
  320.    # Return only files that are signed with the users' certificate (as returned by Get-UserCertificate).
  321.    [switch]$MineOnly
  322. ,
  323.    [Parameter()]
  324.    # Return only files that are NOT signed with the users' certificate (as returned by Get-UserCertificate).
  325.    [switch]$NotMineOnly
  326. ,
  327.    [Parameter()]
  328.    [Alias("HashMismatch")]
  329.    # Return only files with signatures that are broken (where the file has been edited, and the hash doesn't match).
  330.    [switch]$BrokenOnly
  331. ,
  332.    [Parameter()]
  333.    # Returns the files that are Valid OR signed with the users' certificate (as returned by Get-UserCertificate).
  334.    #
  335.    # That is, TrustedOnly returns files returned by -ValidOnly OR -MineOnly (if you specify both parameters, you get only files that are BOTH -ValidOnly AND -MineOnly)
  336.    [switch]$TrustedOnly
  337. ,
  338.    [Parameter()]
  339.    # Return only files that are "Valid": This means signed with any cert where the certificate chain is verifiable to a trusted root certificate.  This may or may not include files signed with the user's certificate.
  340.    [switch]$ValidOnly
  341. ,
  342.    [Parameter()]
  343.    # Return only files that doesn't have a "Valid" signature, which includes files that aren't signed, or that have a hash mismatch, or are signed by untrusted certs (possibly including the user's certificate).
  344.    [switch]$InvalidOnly
  345. ,
  346.    [Parameter()]
  347.    # Return only signable files that aren't signed at all. That is, only files that support Subject Interface Package (SIP) but aren't signed.
  348.    [switch]$UnsignedOnly
  349.  
  350. )
  351. PROCESS {
  352.    if(!(Test-Path -PathType Leaf $FilePath)) {
  353.       # if($ErrorAction -ne "SilentlyContinue") {
  354.       #    Write-Error "Specified Path is not a File: '$FilePath'"
  355.       # }
  356.    } else {
  357.  
  358.       foreach($sig in Get-AuthenticodeSignature -FilePath $FilePath) {
  359.      
  360.       # Broken only returns ONLY things which are HashMismatch
  361.       if($BrokenOnly   -and $sig.Status -ne "HashMismatch")
  362.       {
  363.          Write-Debug "$($sig.Status) - Not Broken: $FilePath"
  364.          return
  365.       }
  366.      
  367.       # Trusted only returns ONLY things which are Valid
  368.       if($ValidOnly    -and $sig.Status -ne "Valid")
  369.       {
  370.          Write-Debug "$($sig.Status) - Not Trusted: $FilePath"
  371.          return
  372.       }
  373.      
  374.       # AllValid returns only things that are SIGNED and not HashMismatch
  375.       if($TrustedOnly  -and (($sig.Status -ne "HashMismatch") -or !$sig.SignerCertificate) )
  376.       {
  377.          Write-Debug "$($sig.Status) - Not Valid: $FilePath"
  378.          return
  379.       }
  380.      
  381.       # NOTValid returns only things that are SIGNED and Valid
  382.       if($InvalidOnly  -and ($sig.Status -eq "Valid"))
  383.       {
  384.          Write-Debug "$($sig.Status) - Valid: $FilePath"
  385.          return
  386.       }
  387.      
  388.       # Unsigned returns only things that aren't signed
  389.       # NOTE: we don't test using NotSigned, because that's only set for .ps1 or .exe files??
  390.       if($UnsignedOnly -and $sig.SignerCertificate )
  391.       {
  392.          Write-Debug "$($sig.Status) - Signed: $FilePath"
  393.          return
  394.       }
  395.      
  396.       # Mine returns only things that were signed by MY CertificateThumbprint
  397.       if($MineOnly     -and (!($sig.SignerCertificate) -or ($sig.SignerCertificate.Thumbprint -ne $((Get-UserCertificate).Thumbprint))))
  398.       {
  399.          Write-Debug "Originally signed by someone else, thumbprint: $($sig.SignerCertificate.Thumbprint)"
  400.          Write-Debug "Does not match your default certificate print: $((Get-UserCertificate).Thumbprint)"
  401.          Write-Debug "     $FilePath"
  402.          return
  403.       }
  404.  
  405.       # NotMine returns only things that were signed by MY CertificateThumbprint
  406.       if($NotMineOnly  -and (!($sig.SignerCertificate) -or ($sig.SignerCertificate.Thumbprint -eq $((Get-UserCertificate).Thumbprint))))
  407.       {
  408.          if($sig.SignerCertificate) {
  409.             Write-Debug "Originally signed by you, thumbprint: $($sig.SignerCertificate.Thumbprint)"
  410.             Write-Debug "Matches your default certificate print: $((Get-UserCertificate).Thumbprint)"
  411.             Write-Debug "     $FilePath"
  412.          }
  413.          return
  414.       }
  415.      
  416.       if(!$BrokenOnly  -and !$TrustedOnly -and !$ValidOnly -and !$InvalidOnly -and !$UnsignedOnly -and !($sig.SignerCertificate) )
  417.       {
  418.          Write-Debug "$($sig.Status) - Not Signed: $FilePath"
  419.          return
  420.       }
  421.      
  422.       get-childItem $sig.Path
  423.    }}
  424. }
  425. }
  426. Export-ModuleMember Set-AuthenticodeSignature, Get-AuthenticodeSignature, Test-Signature, Select-Signed, Get-UserCertificate

Submit a correction or amendment below (
click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:


Remember me