SecretManagement icon indicating copy to clipboard operation
SecretManagement copied to clipboard

Set-AuthenticodeSignature should support Key Vault certificates

Open clairernovotny opened this issue 7 years ago • 19 comments

Today, signing PowerShell scripts/modules requires a certificate to be present in the certificate store or via a PFX file.

There's a growing number of situations where these are not available -- for example, if the key is stored in Key Vault an RSA-HSM key. There's no way to get a private key out.

There should be a way for PowerShell code signing to either use the KeyVault SignAsync API directly or at least have an extension mechanism where the sign digest can be externalized and someone else can write an adapter.

This also has the added benefit of working cross platform as the crypto itself is done in the HSM.

clairernovotny avatar May 05 '17 16:05 clairernovotny

Already some more updates on now Additional APIs? I want to use remove-item and Get-ItemProperty in my VSTeam SHiPS provider.

stefanstranger avatar Nov 27 '17 18:11 stefanstranger

@jianyunt I think we should split this out into smaller feature requests:

  • Support Get-Help
  • Support Invoke-Item
  • Support Get-ItemProperty
  • Support Remove-Item
  • Support Remove-ItemProperty
  • Support Copy-Item
  • Support Copy-ItemProperty
  • Support Clear-Item
  • Support Clear-ItemProperty
  • Support Move-Item
  • Support Move-ItemProperty
  • Support New-Item
  • Support New-ItemProperty
  • Support Rename-Item
  • Support Rename-ItemProperty
  • Support Set-Item
  • Support Set-ItemProperty
  • Support Get-FormatData and possibly auto-generating .format.ps1xml files, possibly via the Prose Framework https://github.com/Microsoft/prose
  • Support Update-TypeData
  • Support Get-Content
  • Support Set-Content

I believe this is the full set of possible PowerShell Provider functions.

What do you think? Is there a reason these all need to be tackled at once?

jzabroski avatar Feb 27 '18 15:02 jzabroski

Strong concur that tracking and adding support for these individually will lead to faster releases and more use for folks. Some of the above (New-Item, Remove-Item, and Copy-Item, for example) are much more valuable to a wider audience.

michaeltlombardi avatar Apr 09 '18 18:04 michaeltlombardi

Get-Content / Set-Content also.

oising avatar Apr 09 '18 18:04 oising

I updated my post to include Get-Content and Set-Content. Good idea. Is there a master list somewhere of "cool commands" that proxy for the old PS Drives feature? I compiled mine through scouring the Internet.

jzabroski avatar Apr 09 '18 19:04 jzabroski

@jzabroski we do not have a list. Your list listed above can be considered as master lit. Agreed we should create separate items so the community may have chance to grab a few too.

jianyunt avatar Apr 09 '18 20:04 jianyunt

@jianyunt The full list is here: https://github.com/PowerShell/PowerShell/blob/1be3f4cc0e465ae11ad8e59e9060f5a59e4762eb/src/System.Management.Automation/utils/CoreProviderCmdlets.cs

jzabroski avatar Apr 17 '19 11:04 jzabroski

Does anyone know if this is actively being worked on? Is it a case of adding in functions on the leaf and node classes, e.g. can it be done in the SHiPSBase so child classes inherit on the appropriate classes. Similar to https://github.com/PowerShell/SHiPS/blob/19b466bd0f000674e4ee787ad82d637b34360fd9/src/Microsoft.PowerShell.SHiPS/Node/SHiPSDirectory.cs#L35 ?

I'll have a play in the meantime and see how I get on.

rhysjtevans avatar Apr 25 '19 08:04 rhysjtevans

@rhysjtevans You'd need to add it there as well as add the actual implementation for it in SHiPSProvider.

For example, if you wanted to add support for *-Content you'd need to add (something like) these to SHiPSLeaf:

public virtual object[] GetContent() => null;

public virtual void SetContent(object[] value) { }

public virtual void ClearContent() { }

And also the provider side implementation that utilizes SHiPSLeaf:

public override IContentReader GetContentReader(string path)
{
    // Create a content reader that uses SHiPSLeaf.
}

public override IContentWriter GetContentWriter(string path)
{
    // Create a content writer that uses SHiPSLeaf.
}

public override void ClearContent(string path)
{
    // Call SHiPSLeaf.ClearContent
}

Also dynamic params.

SeeminglyScience avatar Apr 25 '19 12:04 SeeminglyScience

awesome, Nice one @SeeminglyScience. That helps. I'm working on a couple of other projects so may not get round to this for a while yet.

rhysjtevans avatar May 01 '19 08:05 rhysjtevans

The approach I took when I wrote psproviderframework was to add two new cmdlets to encapsulate the idea of a content reader and a content writer, making it easier to abstract for differing backing stores - different nodes in the tree may require a different strategy for read/write. Each cmdlet took three scriptblocks as parameters representing read, seek and close operations respectively, just like a regular stream. Then, in the script-based provider definition, you provide functions for GetContentReader and GetContentWriter and construct readers and writers to return to the framework. Perhaps someone can take these ideas and work them into SHiPS. I wrote this about ten years ago, so powershell classes weren't around at the time, so it's all scriptblock based and relies on closures to capture state in the defining module.

Here's an example definition script of a simple provider that allows get-content and set-content against its nodes:

Import-Module c:\projects\PowerShell\PSProvider\PSProviderFramework\bin\Debug\PSProviderFramework.dll

if (Get-PSDrive data -ea 0) { remove-psdrive data }

# create new module representing our provider definition and pass to our hosting provider
New-PSDrive data ContainerScriptProvider -Root / -ModuleInfo $(New-Module -Name test -Args @{ 
		
		# our backing store
        a = "this is node a."
        b = "this is node b."
        c = "this is node c."
        d = "this is node d."
	
    } -ScriptBlock {
	
        param($data)
    
        function GetContentReader($path) {
            $psprovider.writeverbose("getcontentreader '$path'")

            # initialize for read operation
            $item = $data[$path]
            $content = [char[]]$item
            $position = 0
        
            # should close around our locals, esp. $position
            # to support concurrent content readers. 
            & {
                # create a new content reader and return it
                New-ContentReader -OnRead {
                    param([long]$count)
				
                    # this implementation returns a string where $count represents number of char to return
                    # at a time; you may choose to return whatever you like, and treat $count in any way you feel
                    # is appropriate. All that matters is that you return an array. Return an empty array to signify
                    # the end of the stream.
				
                    # yes, i could use stringbuilder here but i figure the algorithm is more general purpose for a sample
                    # as this could be easily adopted for byte arrays.
                    $remaining = $content.length - $position
				
                    if ($remaining -gt 0) {
                
                        if ($count -gt $remaining) {
                            $len = $remaining
                        }
                        else {
                            $len = $count
                        }
                        $output = new-object char[] $len
                    
                        [array]::Copy($content, $position, $output, 0, $len)
                        $position += $len
                    
                        @($output -join "")

                    }
                    else {
                
                        # end stream, return empty array
                        write-verbose "read: EOF" -verbose
                        @()
                    }

                } -OnSeek {                
                    param([long]$offset, [io.seekorigin]$origin)
                    write-verbose "seek: $offset origin: $origin" -verbose
            
                } -OnClose {
                    # perform any cleanup you like here.
                    write-verbose "read: close!" -verbose
                }
            }.getnewclosure() # capture state from module
        }

        function GetContentWriter($path) {
            $psprovider.writeverbose("getcontentwriter '$path'")
                
            # initialize for write operation
            $item = $data[$path]
            $position = 0
        
            & {
                New-ContentWriter -OnWrite {
                    param([collections.ilist]$content)
                
                    write-verbose "write: $($content.length) element(s)." -verbose
                
                    $content
                
                } -OnSeek {
                    # seek must be implemented to support -Append, Add-Content etc
                    param([long]$offset, [io.seekorigin]$origin)
                    write-verbose "seek: $offset origin: $origin" -verbose
                
                    switch ($origin) {
                        "end" {
                            $position = $item.length + $offset
                            write-verbose "seek: new position at $position" -verbose
                        }
                        default {
                            write-warning "unsupported seek."
                        }
                    }
                
                } -OnClose {
                    # perform any cleanup you like here.
                    write-verbose "write: close!" -verbose            
                }
            }.getnewclosure() # capture state from module
        }
    
        function GetItem($path) {
            $psprovider.writeverbose("getitem '$path'")
		
            if ($path) {
            
                if ($data[$path]) {
                    $psprovider.writeitemobject($data[$path], $path, $false)
                }

            }
            else {

                # root
                $psprovider.writeitemobject($data.values, "/", $true)
            }
        }

        function ItemExists($path) {

            if ($path) {

                $psprovider.writeverbose("item exists $path")
                $data.containskey($path)

            }
            else {

                # root always exists
                $true
            }
        }

        function GetChildNames($path, $returnContainers) {
		
            $psprovider.writeverbose("getchildnames '$path' $returnContainers")
		
            if ($path) {
                if ($data[$path]) {
                    $psprovider.writeitemobject($path, $path, $false)
                }
            }
            else {
                $data.keys | % { $psprovider.writeitemobject($_, [string]$_, $false) }
            }
        }

        function GetChildItems($path, $recurse) {

            $psprovider.writeverbose("getchildnames '$path' $returnContainers")

            if ($path) {
                $psprovider.writeitemobject($data[$path], $_, $false)
            }
            else {
                $data.keys | % {
                    $psprovider.writeitemobject($data[$_], $_, $false)
                }
            }
        }

        function ClearItem($path) {
            $psprovider.writeverbose("clearitem '$path'")
        }
    })

get-content data:\a -verbose -ReadCount 8

See: https://github.com/oising/psprovider/blob/master/Trunk/PSProviderFramework/Scripts/harness.ps1

oising avatar May 01 '19 15:05 oising

I even got as far as providing transaction support ;)

namespace PSProviderFramework
{
    [CmdletProvider("TransactedTreeScriptProvider", ProviderCapabilities.ShouldProcess | ProviderCapabilities.Transactions)]
    public class TransactedTreeScriptProvider : TreeScriptProvider {
        protected override TReturn InvokeFunction<TReturn>(string function, params object[] parameters) {
            TReturn returnValue;

            // push correct provider thread context for this call
            using (PSProviderContext<TransactedTreeScriptProvider>.Enter(this)) {
                returnValue = PSProviderContext<TransactedTreeScriptProvider>
                    .InvokeFunctionInternal<TReturn>(function, parameters);
            } // pop context

            return returnValue;
        }

        protected override void InvokeFunction(string function, params object[] parameters) {
            // push correct provider thread context for this call
            using (PSProviderContext<TransactedTreeScriptProvider>.Enter(this)) {
                PSProviderContext<TransactedTreeScriptProvider>
                    .InvokeFunctionInternal<object>(function, parameters);
            } // pop context
        }
    }

    [CmdletProvider("TreeScriptProvider", ProviderCapabilities.ShouldProcess)]
    public class TreeScriptProvider : NavigationCmdletProvider, IScriptProvider, IContentCmdletProvider, IPropertyCmdletProvider
    {
    // ...

... as well as properties read/write, container, tree etc. In fact, I think I had covered every aspect of providers. And they haven't changed since then.

oising avatar May 01 '19 15:05 oising

@oising Thanks for sharing your knowledge, wisdom, and design tips!

jzabroski avatar May 01 '19 15:05 jzabroski