Datum.ProtectedData.psm1

function Invoke-ProtectedDatumAction {
    <#
    .SYNOPSIS
    Action that decrypt the secret when the Datum Handler is triggered
 
    .DESCRIPTION
    When Datum uses this handler and a piece of data pass the associated filter, this
    action will decrypt the data.
 
    .PARAMETER InputObject
    Datum data to be decrypted
 
    .PARAMETER PlainTextPassword
    !! FOR TESTING ONLY!!
    Plain text password used for decrypting the password when you want to easily test.
    You can configure the password in the Datum.yml file in the DatumHandler section when
    doing tests.
 
    .PARAMETER Certificate
    Provide the Certificate in a format supported by Dave Wyatt's ProtectedData module:
    It can be a thumbprint, certificate file, path to a certificate file or to cert provider...
 
    .PARAMETER Header
    Header of the Datum data string that encapsulates the encrypted data. The default is [ENC= but can be
    customized (i.e. in the Datum.yml configuration file)
 
    .PARAMETER Footer
    Footer of the Datum data string that encapsulates the encrypted data. The default is ]
 
    .EXAMPLE
    $objectToDecrypt | Invoke-ProtectedDatumAction
 
    .NOTES
    The arguments you can set in the Datum.yml is directly related to the arguments of this function.
 
    #>

    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText','')]
    Param (
        # Serialized Protected Data represented on Base64 encoding
        [Parameter(
            Mandatory
            , Position = 0
            , ValueFromPipeline
            , ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $InputObject,

        # By Password only for development / Test purposes
        [Parameter(
            ParameterSetName = 'ByPassword'
            , Mandatory
            , Position = 1
            , ValueFromPipelineByPropertyName
        )]
        [String]
        $PlainTextPassword,

        # Specify the Certificate to be used by ProtectedData
        [Parameter(
            ParameterSetName = 'ByCertificate'
            , Mandatory
            , Position = 1
            , ValueFromPipelineByPropertyName
        )]
        [String]
        $Certificate,

        # Number of columns before inserting newline in chunk
        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [String]
        $Header = '[ENC=',

        # Number of columns before inserting newline in chunk
        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [String]
        $Footer = ']'
    )
    Write-Debug "Decrypting Datum using ProtectedData"
    $params = @{}
    foreach ($ParamKey in $PSBoundParameters.keys) {
        if ($ParamKey -in @('InputObject', 'PlainTextPassword')) {
            switch ($ParamKey) {
                'PlainTextPassword' { $params.add('password', (ConvertTo-SecureString -AsPlainText -Force $PSBoundParameters[$ParamKey])) }
                'InputObject' { $params.add('Base64Data', $InputObject) }
            }
        }
        else {
            $params.add($ParamKey, $PSBoundParameters[$ParamKey])
        }
    }

    UnProtect-Datum @params

}

#Requires -Modules ProtectedData

function Protect-Datum {
    <#
    .SYNOPSIS
    Protects an object into an encrypted string ready to use in a text file (yaml, JSON, PSD1)
 
    .DESCRIPTION
    This command will serialize (i.e. Export-CLIXml) and object, base 64 encode it and then encrypt
    so that it can be used as a string in a text-based file, like a Datum config file.
 
    .PARAMETER InputObject
    The object to provide to be encrypted and secured.
 
    .PARAMETER Password
    ! FOR TESTING ONLY! Password to encrypt the data.
 
    .PARAMETER Certificate
    Certificate as supported by Dave Wyatt's ProtectedData module: Certificate File, thumbprint, path to file...
 
    .PARAMETER MaxLineLength
    Allow to format somehow the line so that the blob of text is spread on several lines.
 
    .PARAMETER Header
    Adds an encapsulation Header, [ENC= by default
 
    .PARAMETER Footer
    Adds an encapsulation footer, ] by default
 
    .PARAMETER NoEncapsulation
    Generate the encrypted data block without encapsulating with header/footer.
    Useful to test.
 
    .EXAMPLE
    Protect-Datum -InputObject $credential -Password P@ssw0rd
 
    .NOTES
    This function is a helper to build your file containing a secret.
    #>

    [CmdletBinding()]
    [OutputType([String])]
    Param (
        # Serialized Protected Data represented on Base64 encoding
        [Parameter(
            Mandatory
            , Position = 0
            , ValueFromPipeline
            , ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [PSObject]
        $InputObject,

        # By Password only for development / Test purposes
        [Parameter(
            ParameterSetName = 'ByPassword'
            , Mandatory
            , Position = 1
            , ValueFromPipelineByPropertyName
        )]
        [System.Security.SecureString]
        $Password,

        # Specify the Certificate to be used by ProtectedData
        [Parameter(
            ParameterSetName = 'ByCertificate'
            , Mandatory
            , Position = 1
            , ValueFromPipelineByPropertyName
        )]
        [String]
        $Certificate,

        # Number of columns before inserting newline in chunk
        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [Int]
        $MaxLineLength = 100,

        # Number of columns before inserting newline in chunk
        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [String]
        $Header = '[ENC=',

        # Number of columns before inserting newline in chunk
        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [String]
        $Footer = ']',

        # Number of columns before inserting newline in chunk
        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [Switch]
        $NoEncapsulation

    )

    begin {
    }

    process {
        Write-Verbose "Deserializing the Object from Base64"

        $ProtectDataParams = @{
            InputObject = $InputObject
        }
        Write-verbose "Calling Protect-Data $($PSCmdlet.ParameterSetName)"
        Switch ($PSCmdlet.ParameterSetName) {
            'ByCertificate' { $ProtectDataParams.Add('Certificate', $Certificate)}
            'ByPassword' { $ProtectDataParams.Add('Password', $Password)      }
        }

        $securedData = Protect-Data @ProtectDataParams
        $xml = [System.Management.Automation.PSSerializer]::Serialize($securedData, 5)
        $bytes = [System.Text.Encoding]::UTF8.GetBytes($xml)
        $Base64String = [System.Convert]::ToBase64String($bytes)

        if ($MaxLineLength -gt 0) {
            $Base64DataBlock = [regex]::Replace($Base64String, "(.{$MaxLineLength})", "`$1`r`n")
        }
        else {
            $Base64DataBlock = $Base64String
        }
        if (!$NoEncapsulation) {
            $Header, $Base64DataBlock, $Footer -join ''
        }
        else {
            $Base64DataBlock
        }
    }
}

function Test-ProtectedDatumFilter {
    <#
    .SYNOPSIS
    Filter function to verify if it's worth triggering the action for the data block.
 
    .DESCRIPTION
    This function is run in the ConvertTo-Datum function of the Datum module on every pass,
    and when it returns true, the action of the handler is called.
 
    .PARAMETER InputObject
    Object to test to decide whether to trigger the action or not
 
    .EXAMPLE
    $object | Test-ProtectedDatumFilter
 
    #>

    Param(
        [Parameter(
            ValueFromPipeline
        )]
        $InputObject
    )

    $InputObject -is [string] -and $InputObject.Trim() -match "^\[ENC=[\w\W]*\]$"
}

#Requires -Modules ProtectedData

function Unprotect-Datum {
    <#
    .SYNOPSIS
    Decrypt a previously encrypted object
 
    .DESCRIPTION
    This command decrypts the string representation of an object previously encrypted
    by Protect-Datum. You can decrypt a credential object, a secure string or simply
    a string. It uses Dave Wyatt's ProtectedData module under the hood.
 
    .PARAMETER Base64Data
    The encrypted data is represented in a Base 64 string to be easily stored in a text document.
    This is the input to be decrypted and restored as an object.
 
    .PARAMETER Password
    ! FOR TESTS ONLY !
    You can use a password to encrypt and decrypt the data you want to secure when doing test
    with this module
 
    .PARAMETER Certificate
    You can pass the certificate (thumbprint, file, file path, cert provider path...) containing the
    private key to be used to decrypt the secured data.
 
    .PARAMETER Header
    Header to strip off when encapsulated in a file. default is [ENC=
 
    .PARAMETER Footer
    Footer to strip off when encapsulated in a file. default is ]
 
    .PARAMETER NoEncapsulation
    Switch to attempt the decryption when there is no encapsulation
 
    .EXAMPLE
    $encryptedstring | Unprotect-Datum -NoEncapsulation -Password P@ssw0rd
 
    #>

    [CmdletBinding()]
    [OutputType([PSObject])]
    Param (
        # Serialized Protected Data represented on Base64 encoding
        [Parameter(
            Mandatory
            , Position = 0
            , ValueFromPipeline
            , ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $Base64Data,

        # By Password only for development / Test purposes
        [Parameter(
            ParameterSetName = 'ByPassword'
            , Mandatory
            , Position = 1
            , ValueFromPipelineByPropertyName
        )]
        [System.Security.SecureString]
        $Password,

        # Specify the Certificate to be used by ProtectedData
        [Parameter(
            ParameterSetName = 'ByCertificate'
            , Mandatory
            , Position = 1
            , ValueFromPipelineByPropertyName
        )]
        [String]
        $Certificate,

        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [String]
        $Header = '[ENC=',

        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [String]
        $Footer = ']',

        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [Switch]
        $NoEncapsulation
    )

    begin {
    }

    process {
        if (!$NoEncapsulation) {
            Write-Verbose "Removing $header DATA $footer "
            $Base64Data = $Base64Data -replace "^$([regex]::Escape($Header))" -replace "$([regex]::Escape($Footer))$"
        }

        Write-Verbose "Deserializing the Object from Base64"
        $bytes = [System.Convert]::FromBase64String($Base64Data)
        $xml = [System.Text.Encoding]::UTF8.GetString($bytes)
        $obj = [System.Management.Automation.PSSerializer]::Deserialize($xml)
        $UnprotectDataParams = @{
            InputObject = $obj
        }
        Write-verbose "Calling Unprotect-Data $($PSCmdlet.ParameterSetName)"
        Switch ($PSCmdlet.ParameterSetName) {
            'ByCertificae' { $UnprotectDataParams.Add('Certificate', $Certificate)}
            'ByPassword' { $UnprotectDataParams.Add('Password', $Password)      }
        }
        Unprotect-Data @UnprotectDataParams
    }

}