CAM.psm1

# CONSTRUCTORS

<#
.SYNOPSIS
    Creates a new CAMConfig object
.DESCRIPTION
    Use this function to create a new CAMConfig object to pass to functions within the module if you dont want to read from a configuration file.
.PARAMETER AADApplicationId
    The id of your AAD Application that has access to the KeyVault.
.PARAMETER AADApplicationKey
    An encrypted key for authenticating to the AAD Application. You will need either a key or a certificate and password to authenticate to the AAD Application.
.PARAMETER TenantId
    The TenantId of the Azure Service Principal registered to your AAD Application.
.PARAMETER KeyVaultCertificate
    The name of the certificate file you are using to authenticate to the AAD Application.
.PARAMETER KeyVaultCertificatePassword
    The password of the certificate file you are using to authenticate to the AAD Application.
.PARAMETER KeyVault
    The KeyVault you wish to retrieve certificates from.
.PARAMETER Environment
    The environment name for the current service the Module is being run from.
#>

function New-CAMConfig() {
    param(
        [parameter(mandatory=$true)]
        [string]$AADApplicationId,

        [parameter(ParameterSetName="KeyAuth", mandatory=$true)]
        [parameter(mandatory=$false)]
        [AllowEmptyString()]
        [SecureString]$AADApplicationKey,

        [parameter(mandatory=$true)]
        [string]$TenantId,

        [parameter(ParameterSetName="CertificateAuth", mandatory=$true)]
        [parameter(mandatory=$false)]
        [AllowEmptyString()]
        [string]$KeyVaultCertificate,

        [parameter(ParameterSetName="CertificateAuth", mandatory=$true)]
        [parameter(mandatory=$false)]
        [AllowEmptyString()]
        [SecureString]$KeyVaultCertificatePassword,

        [parameter(mandatory=$true)]
        [string]$KeyVault,

        [parameter(mandatory=$true)]
        [string]$Environment
    )

    return [PSCustomObject]@{
        PSTypeName = "CAMConfig" 
        AADApplicationId = $AADApplicationId
        AADApplicationKey = $AADapplicationKey
        TenantId = $TenantId
        KeyVaultCertificate = $KeyVaultCertificate
        KeyVaultCertificatePassword = $KeyVaultCertificatePassword
        KeyVault = $KeyVault
        Environment = $Environment
    }
}

# END CONSTRUCTORS

# SETUP FUNCTIONS

# Script level variable for fallbacks during development, should store these in config or pass in to individual functions.
$script:CAMConfig = New-CamConfig -AADApplicationId "MyAADApp" -AADApplicationKey ("MyAADKey" | ConvertTo-SecureString -AsPlainText -force) -TenantId "12345" -KeyVault "MyKeyVault" -Environment "PROD"

<#
.SYNOPSIS
    Install AAD Application certificate from an optionally given path
.DESCRIPTION
    Install the certificate you will use to authenticate to your AAD Application. If the certificate is not stored in the same directory as the module pass in the path to the directory it is in with the "Path" parameter.
.PARAMETER Path
    The Path to the directory that houses the certificate you will use to authenticate to your AAD Application. This defaults to the current directory.
.PARAMETER CAMConfig
    (optional) A configuration object used to override the fallback variable and any present CAMConfig file.
.EXAMPLE
    C:\PS> Install-AADAppCertificate -Path "C:\Certificates\AADApp" -CAMConfig $CustomConfig
#>

function Install-AADAppCertificate() {
Param(
    [parameter()]
    [string]$Path = (Get-Item -Path ".\").FullName,
    [parameter()]
    [PSTypeName("CAMConfig")]$CAMConfig = $script:CAMConfig 
)
    if (Test-Path "$($Path)\$($CAMConfig.KeyVaultCertificate).pfx") {
        try {
            $Pfx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
            $Pfx.Import("$($Path)\$($CAMConfig.KeyVaultCertificate).pfx", $CAMConfig.KeyVaultCertificatePassword, "PersistKeySet")
            if (-not $Pfx.FriendlyName) {
                $Pfx.FriendlyName = $CAMConfig.KeyVaultCertificate
            }

            $Store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "LocalMachine")
            $Store.Open("MaxAllowed")
            $Store.Add($Pfx)
            $Store.Close()
            $PfxFriendlyName = $Pfx.FriendlyName
            $Pfx.Dispose()
            return $PfxFriendlyName
        }
        catch {
            throw "certificate $($CAMConfig.KeyVaultCertificate) could not be imported with given password"
        }
    }
    else {
        throw "AAD App certificate was not found at $($Path)"
    }
}

<#
.SYNOPSIS
    Read the CAMconfig file from an optionally given path, or pass in your own custom configuration object.
.DESCRIPTION
    Read the CAMconfig file from an optionally given path, or pass in your own custom configuration object to override the fallback variable and any present CAMconfig file.
.PARAMETER Path
    The Path to the directory that houses the CAMConfig you will use. This defaults to the current directory.
.PARAMETER CAMConfig
    (optional) A configuration object used to override the fallback variable and any present configuration files.
.EXAMPLE
    C:\PS> Read-CAMConfig -Path "C:\CAM\Config" -CAMConfig $CustomConfig
#>

function Read-CAMConfig() {
param(
    [parameter()]
    [string]$Path = (Get-Item -Path ".\").FullName,
    [parameter()]
    [PSTypeName("CAMConfig")]$CAMConfig = $script:CAMConfig 
)
    if ($CAMConfig -ne $script:CAMConfig) {
        $script:CAMConfig.AADApplicationID = $CAMConfig.AADApplicationId
        $script:CAMConfig.AADApplicationKey = $CAMConfig.AADApplicationkey
        $script:CAMConfig.TenantId = $CAMConfig.TenantId
        $script:CAMConfig.KeyVaultCertificate = $CAMConfig.KeyVaultCertificate
        $script:CAMConfig.KeyVaultCertificatePassword = $CAMConfig.KeyVaultCertificatePassword
        $script:CAMConfig.KeyVault = $CAMConfig.KeyVault
        $script:CAMConfig.Environment = $CAMConfig.Environment
        return
    }
    if (Test-Path "$($Path)\CAMConfig.json") {
        try {
            $Json = Get-Content -Raw -Path "$($Path)\CAMConfig.json" | ConvertFrom-Json
            # reset hardcoded fallback values
            $script:CAMConfig.AADApplicationID = $Json.AADApplicationId
            if ($Json.AADApplicationkey) {
                $script:CAMConfig.AADApplicationkey = ($Json.AADApplicationkey | ConvertTo-SecureString -AsPlainText -Force)
            }
            $script:CAMConfig.TenantId = $Json.TenantId
            $script:CAMConfig.KeyVaultCertificate = $Json.KeyVaultCertificate
            if ($Json.KeyVaultCertificatePassword) {
                $script:CAMConfig.KeyVaultCertificatePassword = ($Json.KeyVaultCertificatePassword | ConvertTo-SecureString -AsPlainText -Force)
            }
            $script:CAMConfig.KeyVault = $Json.KeyVault
            $script:CAMConfig.Environment = $Json.Environment
            return $true
        }
        catch {
            write-error "Unable to read config at $($Path)\CAMConfig.json, defaulting to hardcoded fallback values." 
        }
    }
    else {
        write-error "Unable to read config at $($Path)\CAMConfig.json, defaulting to hardcoded fallback values."
    }
}

<#
.SYNOPSIS
    Schedule CAM to run every 5 minutes
.DESCRIPTION
    Create a scheduled task that will run the CAM module's Install-KVCertificates function every 5 minutes.
.PARAMETER Path
    The Path to the directory that houses the CAM module you will use. This defaults to the current directory.
.EXAMPLE
    C:\PS> New-CAMSchedule -Path "C:\CAM"
#>

function New-CAMSchedule() {
param(
    [parameter()]
    [string]$Path = (Get-Item -Path ".\").FullName
)
    try {
        $action = New-ScheduledTaskAction -Execute 'Powershell.exe' -WorkingDirectory "$Path" -Argument "Import-Module .\CAM.psm1; Install-KVCertificates"

        $trigger =  New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5) 

        Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "CAM" -RunLevel Highest
        return $true
    }
    catch {
        write-error "Unable to schedule CAM task. Exception $_"
    }
}

# END SETUP FUNCTIONS

# AUTH FUNCTIONS

<#
.SYNOPSIS
    Function for authenticating using user profile
.DESCRIPTION
    Authenticate to azure using your personal azure account credentials. If there is a .ctx or .json azure profile file available in the directory the function is run from it will default to authenticating with it.
.EXAMPLE
    C:\PS> Authenticate-WithUserProfile
#>

function Authenticate-WithUserProfile() {
    # Log in to Azure
    $Path = (Get-Item -Path ".\").FullName
    if (Test-Path "$Path\myAzureRmProfile.json") {
        Select-AzureRmProfile -Path "$Path\myAzureRmProfile.json" -ErrorAction Stop
    }
    elseif (Test-Path "$Path\profile.ctx") {
        Import-AzureRmContext -Path "$Path\profile.ctx" -ErrorAction Stop
    }
    else {
        write-output 'Please log into Azure now'
        Login-AzureRMAccount -ErrorAction stop
    }
}

<#
.SYNOPSIS
    Function for authenticating to AAD through AAD app certificate
.DESCRIPTION
    Authenticate to AAD Application using a certificate that has been whitelisted with an applicable service principal.
.PARAMETER CAMConfig
    (optional) A configuration object used to override the fallback variable and any present configuration files.
.EXAMPLE
    C:\PS> Authenticate-WithCertificate -CAMConfig $CustomConfig
#>

function Authenticate-WithCertificate() {
param(
    [parameter()]
    [PSTypeName("CAMConfig")]$CAMConfig = $script:CAMConfig
)
    try {
        $KeyVaultCertificateThumbprint = (Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.FriendlyName -match $CAMConfig.KeyVaultCertificate}).Thumbprint
        Login-AzureRmAccount -ServicePrincipal -CertificateThumbprint $KeyVaultCertificateThumbprint -ApplicationId $CAMConfig.AADApplicationID -TenantId $CAMConfig.TenantId -ErrorAction Stop
    }
    catch {
        throw "Unable to login with Certificate $($CAMConfig.KeyVaultCertificate). Error: $_"
    }
}

<#
.SYNOPSIS
    Function for authenticating to AAD through AAD app key
.DESCRIPTION
    Authenticate to AAD Application using a encrypted key that has been whitelisted with an applicable service principal.
.PARAMETER CAMConfig
    (optional) A configuration object used to override the fallback variable and any present configuration files.
.EXAMPLE
    C:\PS> Authenticate-WithKey -CAMConfig $CustomConfig
#>

function Authenticate-WithKey() {
param(
    [parameter()]
    [PSTypeName("CAMConfig")]$CAMConfig = $script:CAMConfig,
    [parameter()]
    [SecureString]$key = $CAMConfig.AADApplicationKey
)
    try {
        $Credential = New-Object System.Management.Automation.PSCredential($CAMConfig.AADApplicationID, $key)
        Login-AzureRmAccount -Credential $Credential -Tenant $CAMConfig.TenantId -ServicePrincipal -ErrorAction Stop
    }
    catch {
        throw "Unable to login with Key. Error: $_"
    }
}

<#
.SYNOPSIS
    Function for trying to authenticate to AAD app with Certificate, Key, or User profile.
.DESCRIPTION
    Authenticate to azure by attempting to authenticate first to an AAD Application with either a whitelisted key or certificate. If that fails, attempt to use local users credentials.
.PARAMETER CAMConfig
    (optional) A configuration object used to override the fallback variable and any present configuration files.
.EXAMPLE
    C:\PS> Authenticate-ToKeyVault -CAMConfig $CustomConfig
#>

function Authenticate-ToKeyVault() {
param(
    [parameter()]
    [PSTypeName("CAMConfig")]$CAMConfig = $script:CAMConfig
)
    Try {
        if ($CAMConfig.KeyVaultCertificate) {
            Authenticate-WithCertificate -CAMConfig $CAMConfig 
        }
        else {
            Authenticate-WithKey -CAMConfig $CAMConfig 
        }
    }
    # If that doesn't work use local user profile
    Catch {
        Authenticate-WithUserProfile
    }
}

# END AUTH FUNCTIONS

<#
.SYNOPSIS
    Install and delete certificates as specified within the manifest file provided or saved in the KeyVault.
.DESCRIPTION
    Ensure the module is authenticated to an AAD Application. Load a manifest file stored in the KeyVault or provided locally. Break apart the manifest into a secret and certificate section.
    Iterate through the secrets and download or delete the certificate as specified in the certificateVersion "deploy" property. Repeat this process for the certificate section.
.PARAMETER LocalManifest
    (optional) The full path to a valid json manifest file to be used in place of a passed in object or manifest available in the KeyVault.
.PARAMETER CAMConfig
    (optional) A configuration object used to override the fallback variable and any present configuration files.
.EXAMPLE
    C:\PS> Install-KVCertificates -LocalManifest "C:\CAM\testingmanifests\test.json" -CAMConfig $CustomConfig
#>
 
function Install-KVCertificates() {
param(
    [parameter()]
    [string]$LocalManifest,
    [parameter()]
    $Manifest,
    [parameter()]
    [PSTypeName("CAMConfig")]$CAMConfig = $script:CAMConfig
)    
    #Read CAMConfig object if present and update fallback values
    if ($CAMConfig -ne $script:CAMConfig) {
        Read-CAMConfig -CAMConfig $CAMConfig | Out-Null
    }
    else {
        Read-CAMConfig | Out-Null
    }

    write-output "CAM: Config loaded"

    #If certificate authentication is being used, install the required certificate
    if ($CAMConfig.KeyVaultCertificate -and $CAMConfig.KeyVaultCertificatePassword) {
        Install-AADAppCertificate -CAMConfig $CAMConfig | Out-Null
    }

    #Authenticate with AAD App and KeyVault
    Authenticate-ToKeyVault -CAMConfig $CAMConfig | Out-Null

    write-output "CAM: Authenticated to KeyVault"

    # local path passed in
    if ($LocalManifest) { 
        $json = Get-Content -Raw -Path "$($localmanifest)" | ConvertFrom-Json 
    }
    # object passed in
    elseif ($Manifest) {
        if ($Manifest.gettype().tostring() -eq "System.Management.Automation.PSCustomObject") {
            $json = $Manifest
        }
        else {
            write-output "CAM: Manifest object was not of type System.Management.Automation.PSCustomObject"
            return
        }
    }
    # retrieve manifest from keyvault
    else {
        # Load manifest from the KeyVault
        $manifestName = "$($CAMConfig.KeyVault)-manifest"
        $manifest = Get-AzureKeyVaultSecret -VaultName $CAMConfig.KeyVault -Name $manifestName -ErrorAction Stop 
        $json = $manifest.SecretValueText | ConvertFrom-Json
    }

    write-output "CAM: Manifest loaded"

    # Iterate through Certificates section
    if ($null -ne $json.certificates) {
        foreach ($Certificate in $json.certificates) {
            $CertificateName = $Certificate.CertName
            $CertificateVersions = $Certificate.CertVersions
            # Iterate through Certificate versions
            foreach ($CertificateVersion in $CertificateVersions) {
                $RetrievedCertificate = Get-AzureKeyVaultCertificate -VaultName $CAMConfig.KeyVault -Name $CertificateName -Version $CertificateVersion.CertVersion
                $downloaded = $false
                # Iterate through deployment array to decide if cert should be installed or deleted
                foreach ($deployment in $CertificateVersion.Deploy) {
                    if ($deployment -eq "True" -or $deployment -eq $CAMConfig.Environment) { 
                        # Install Certificate
                        write-output "CAM: Installing Certificate: $($CertificateName)"
                        Install-KVCertificateObject -CertName $CertificateName -CertVersion $CertificateVersion.CertVersion `
                            -CertStoreName $CertificateVersion.StoreName -CertStoreLocation $CertificateVersion.StoreLocation
                        # Grant user access to private keys
                        if ($null -ne $CertificateVersion.GrantAccess) {
                            Grant-CertificateAccess -CertName $CertificateName -User $CertificateVersion.GrantAccess -CertStoreName $CertificateVersion.StoreName `
                            -CertStoreLocation $CertificateVersion.StoreLocation
                        }
                        $downloaded = $true
                    }
                }
                # Delete Certificate
                if (!$downloaded) {
                    write-output "CAM: Deleting Certificate: $($CertificateName)"
                    Remove-Certificate -certName $CertificateName -CertStoreLocation $CertificateVersion.StoreLocation `
                     -CertStoreName $CertificateVersion.StoreName -certThumbprint $RetrievedCertificate.Thumbprint
                }
            }
        }
    }

    # Iterate through Secrets section
    if ($null -ne $json.Secrets) {
        foreach ($Secret in $json.Secrets) {
            $CertificateName = $Secret.CertName
            $CertificateVersions = $Secret.CertVersions
            # Iterate through Certificate versions
            foreach ($CertificateVersion in $CertificateVersions) {
                $download = $false
                # Iterate through deployment array to decide if cert should be downloaded or deleted
                foreach ($deployment in $CertificateVersion.Deploy) {
                    if ($deployment -eq "True" -or $deployment -eq $CAMConfig.Environment) { 
                        # Install Certificate
                        write-output "CAM: Installing Certificate: $($CertificateName)"
                        Install-KVSecretObject -CertName $CertificateName -CertVersion $CertificateVerion.CertVersion `
                            -CertStoreName $CertificateVersion.StoreName -CertStoreLocation $CertificateVersion.StoreLocation -CAMConfig $CAMConfig
                        # Grant user access to private keys
                        if ($null -ne $CertificateVersion.GrantAccess) {
                            Grant-CertificateAccess -CertName $CertificateName -User $CertificateVersion.GrantAccess -CertStoreName $CertificateVersion.StoreName `
                            -CertStoreLocation $CertificateVersion.StoreLocation
                        }
                        $download = $true
                    }
                }
                # Delete Certificate
                if (!$download) {
                    write-output "CAM: Deleting Certificate: $($CertificateName)"
                    $Thumbprint = Get-SecretThumbprint -CertName $CertificateName -CertVersion $CertificateVersion.CertVersion -CAMConfig $CAMConfig
                    Remove-Certificate -CertName $CertificateName -CertStoreLocation $CertificateVersion.StoreLocation`
                     -CertStoreName $CertificateVersion.StoreName -CertThumbprint $Thumbprint
                }
            }
        }
    }

}

<#
.SYNOPSIS
    This function will download and install a certificate object from a key vault and install it on local machine.
.DESCRIPTION
    This script runs through key vault commands to download a certificate object from a key vault and install it locally.
.PARAMETER CertName
    secret name in key vault
.PARAMETER CertVersion
    (optional) Version GUID of the secret you want to retrieve.
.PARAMETER CertStoreName
    (optional) Certificate Store Name that you would like the certificate installed to. Defaults to "My"
.PARAMETER CertStoreLocation
    (optional) Certificate Store Location that you would like the certificate installed to. Defaults to "LocalMachine"
.PARAMETER CAMConfig
    (optional) A configuration object used to override the fallback variable and any present configuration files.
.EXAMPLE
    C:\PS> Install-KVCertificateObject -CertName "MyCertificate" -CertVersion "0000-0000-0000-0000" `
            -CertStoreName "My" -CertStoreLocation "LocalMachine" -CAMConfig $CustomConfig
#>


function Install-KVCertificateObject() {
param(
    [parameter(Mandatory=$true)]
    [string]$CertName,
    [parameter()]
    [string]$CertVersion,
    [parameter()]
    [string]$CertStoreName = "My",
    [parameter()]
    [string]$CertStoreLocation = "LocalMachine",
    [parameter()]
    $CAMConfig = $script:CAMConfig
)
    if ($CertVersion) {
        $Cert = Get-AzureKeyVaultCertificate -VaultName $CAMConfig.KeyVault -Name $CertName -Version $CertVersion
    }
    else {
        $Cert = Get-AzureKeyVaultCertificate -VaultName $CAMConfig.KeyVault -Name $CertName
    }
    if (-not $Cert) {
        write-output "CAM: Certificate $($certName) does not exist in $($CAMConfig.KeyVault) KeyVault"
        return
    }
    $Cert.Certificate.FriendlyName = $CertName
    $Store = New-Object System.Security.Cryptography.X509Certificates.X509Store($CertStoreName, $CertStoreLocation)
    $Store.Open("MaxAllowed")
    $Store.Add($Cert.Certificate)
    $Store.Close()
    write-output "CAM: Installed Certificate $($CertName) to $CertStoreLocation\$CertStoreName"
}

<#
.SYNOPSIS
    This function will download and install a certificate from a json object that was encrypted and stored as a KeyVault secret.
.DESCRIPTION
    This function will download and install certificates that have been stored in KeyVault as encrypted json objects. These json objects are made up of two properties. "data" which stores the raw certificate data,
    and "password" which stores the password to the Pfx.
.PARAMETER CertName
    Cert name in key vault
.PARAMETER CertVersion
    (optional) Version GUID of the secret you want to retrieve.
.PARAMETER CertStoreName
    (optional) Certificate Store Name that you would like the certificate installed to. Defaults to "My"
.PARAMETER CertStoreLocation
    (optional) Certificate Store Location that you would like the certificate installed to. Defaults to "LocalMachine"
.PARAMETER CAMConfig
    (optional) A configuration object used to override the fallback variable and any present configuration files.
.EXAMPLE
    C:\PS> Install-KVSecretObject -CertName "MyCertificate" -CertVersion "0000-0000-0000-0000" `
            -CertStoreName "My" -CertStoreLocation "LocalMachine" -CAMConfig $CustomConfig
#>
 
function Install-KVSecretObject() {
param(
    [parameter(mandatory=$true)]
    [string]$CertName,
    [parameter()]
    [string]$CertVersion,
    [parameter()]
    [string]$CertStoreName = "My",
    [parameter()]
    [string]$CertStoreLocation = "LocalMachine",
    [parameter()]
    $CAMConfig = $script:CAMConfig
)
    if ($CertVersion) {
        $Secret = Get-PrivateKeyVaultCert -CertName $CertName -CertVersion $CertVersion -CAMConfig $CamConfig
    }
    else {
        $Secret = Get-PrivateKeyVaultCert -CertName $CertName -CAMConfig $CamConfig
    }
    if (-not $Secret) {
        write-output "CAM: Certificate $($certName) does not exist in $($CAMConfig.KeyVault) KeyVault"
        return
    }
    $KvSecretBytes = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Secret.SecretValueText))
    $CertJson = $KvSecretBytes | ConvertFrom-Json
    $Password = $CertJson.password
    $CertBytes = [System.Convert]::FromBase64String($CertJson.data)

    $Pfx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
    try {
        $Pfx.Import($CertBytes, $Password, "PersistKeySet")
    }
    catch {
        write-output "CAM: Certificate $Certname could not be imported with password. Exception: $_"
        return
    }
    $Pfx.FriendlyName = $CertName
    $Store = New-Object System.Security.Cryptography.X509Certificates.X509Store($CertStoreName, $CertStoreLocation)
    $Store.Open("MaxAllowed")
    $Store.Add($Pfx)
    $Store.Close()
    $Pfx.Dispose()
    write-output "CAM: Installed Certificate $($CertName) to $CertStoreLocation\$CertStoreName store"
}

<#
.SYNOPSIS
    This function retrives a KeyVault secret based on the name and version. If the version is not specified it retrieves the most current version.
.PARAMETER CertName
    Cert name in key vault
.PARAMETER CertVersion
    (optional) Version GUID of the secret you want to retrieve.
.PARAMETER CAMConfig
    (optional) A configuration object used to override the fallback variable and any present configuration files.
.EXAMPLE
    C:\PS> Get-PrivateKeyVaultCert -CertName "MyCertificate" -CertVersion "0000-0000-0000-0000" -CAMConfig $CustomConfig
#>

function Get-PrivateKeyVaultCert() {
param(
    [parameter(Mandatory=$true)]
    [string]$CertName,
    [parameter()]
    [string]$CertVersion,
    [parameter()]
    [PSTypeName("CAMConfig")]$CAMConfig = $script:CAMConfig
)
    if ($CertVersion) {
        return Get-AzureKeyVaultSecret -VaultName $CAMConfig.KeyVault -Name $CertName -Version $CertVersion
    }
    else {
        return Get-AzureKeyVaultSecret -VaultName $CAMConfig.KeyVault -Name $CertName
    }
}

<#
.SYNOPSIS
    This function lets you remove a cert from a provided store location and store name.
.DESCRIPTION
    If this function is not provided a thumbprint, it will search the provided store location and store name for certificates with a friendly name that match the provided CertName parameter
    and select the one with the earliest expiry. Once the certificate has been selected by earliest expiry or provided thumbprint, it is removed from the store.
.PARAMETER CertName
    The friendly name of the certificate you would like to remove.
.PARAMETER CertStoreLocation
    The store location where the certificate is installed.
.PARAMETER CertStoreName
    The store name where the certificate is installed.
.PARAMETER CertThumbprint
    (optional) The thumbprint of the certificate you would like to remove. If not provided, the certificate that matches the CertName parameter with the earliest expiry will be selected.
.EXAMPLE
    C:\PS> Remove-Certificate -CertName "MyCertificate" -CertStoreLocation "LocalMachine" -CertStoreName "My" -CertificateThumbprint "0000000000000000000000000000"
#>

function Remove-Certificate() {
param(
    [parameter()]
    [string]$CertName,
    [parameter(mandatory=$true)]
    [string]$CertStoreLocation,
    [parameter(mandatory=$true)]
    [string]$CertStoreName,
    [parameter()]
    [string]$CertThumbprint
)
    try {
        if (-not $CertThumbprint) {
            try {
                #find the certificate with the earliest expiry of those matching the CertName parameter
                $CertLocation = (Get-ChildItem "Cert:\$CertStoreLocation\$CertStoreName" | Where-Object {$_.FriendlyName -match $CertName} | Sort-Object -Property NotAfter)[0].PSPath
                Write-Output "CAM: Certificate Thumbprint not provided for $CertName, attempting to delete certificate with earliest expiry."
            }
            catch {
                write-output "CAM: Certificate $($CertName) does not exist in $($CertStoreLocation)\$($CertStoreName) store"
            }
        }
        else {
            #Create path to cert
            $CertLocation = "Cert:\" + $CertStoreLocation + "\" + $CertStoreName + "\" + $CertThumbprint
        }
        if (test-path $CertLocation) {
            Remove-Item $CertLocation
            write-output "CAM: Certificate $($CertName) deleted from $($CertStoreLocation)\$($CertStoreName) store"
        }
        else {
            write-output "CAM: Certificate $($CertName) does not exist in $($CertStoreLocation)\$($CertStoreName) store"
        }
    }
    catch {
        write-output "CAM: Failed to delete certificate $($CertLocation). Exception: $_" 
    }
}


<#
.SYNOPSIS
    This function grants access to a supplied user for a certificates private key.
.DESCRIPTION
    This function grants access to a supplied user (defaulted to Network Service) for a certificates private key.
.PARAMETER CertName
    The friendly name of the certificate you would like to remove.
.PARAMETER User
    (optional) The user you want to give access to.
.PARAMETER CertStoreName
    The store name where the certificate is installed.
.PARAMETER CertStoreLocation
    The store location where the certificate is installed.
.EXAMPLE
    C:\PS> Grant-CertificateAccess -CertName "MyCertificate" -User "Network Service" -CertStoreLocation "LocalMachine" -CertStoreName "My"
#>

function Grant-CertificateAccess() {
param(
    [parameter(mandatory=$true)]
    [string]$CertName,
    [parameter()]
    [string]$User = "Network Service",
    [parameter()]
    [string]$CertStoreName = "My",
    [parameter()]
    [string]$CertStoreLocation = "LocalMachine"

)
    $Certificate = (Get-ChildItem "Cert:\$CertStoreLocation\$CertStoreName" | Where-Object {$_.FriendlyName -match $CertName}).PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
    if ($Certificate) {
        $keyPath = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\"
        $fullpath = $keypath+$obj
        $acl=Get-Acl -Path $fullPath
        $permission=$User, "Read", "Allow"
        $accessRule=new-object System.Security.AccessControl.FileSystemAccessRule $permission
        $acl.AddAccessRule($accessRule)
        try {
            Set-Acl $fullPath $acl
            Write-Output "CAM: Granted access to $User for certificate $CertName in $CertStoreLocation\$CertStoreName store."
        }
        catch {
            Write-Output "CAM: Unable to grant access to $User for certificate $CertName in $CertStoreLocation\$CertStoreName store. Exception: $_"
        }
    }
    else {
        Write-Output "CAM: Unable to locate certificate $CertName in $CertStoreLocation\$CertStoreName store"
    }
}

<#
.SYNOPSIS
    This function retrieves a thumbprint from a secret object.
.DESCRIPTION
    This function retrieves a secret object from the KeyVault and then returns its thumbprint.
.PARAMETER CertName
    The friendly name of the certificate you would like to remove.
.PARAMETER CertVersion
    (optional) Version GUID of the secret you want to retrieve.
.PARAMETER CAMConfig
    (optional) A configuration object used to override the fallback variable and any present configuration files.
.EXAMPLE
    C:\PS> Get-SecretThumbprint -CertName "MyCertificate" -CertVersion "0000-0000-0000-0000" -CAMConfig $CustomConfig
#>

function Get-SecretThumbprint() {
param(
    [parameter(mandatory=$true)]
    [string]$CertName,
    [parameter()]
    [string]$CertVersion,
    [parameter()]
    [PSTypeName("CAMConfig")]$CAMConfig = $script:CAMConfig
)
    $Secret = Get-AzureKeyVaultSecret -VaultName $CAMConfig.KeyVault -Name $CertName -Version $CertVersion -ErrorAction Stop
    if (-not $Secret) {
        write-output "CAM: Certificate $($certName) does not exist in $($CAMConfig.KeyVault) KeyVault"
        return
    }
    $KvSecretBytes = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Secret.SecretValueText))
    $CertJson = $KvSecretBytes | ConvertFrom-Json
    $Password = $CertJson.password
    $CertBytes = [System.Convert]::FromBase64String($CertJson.data)
    $Pfx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
    try {
        $Pfx.Import($CertBytes, $Password, "PersistKeySet")
    }
    catch {
        write-output "CAM: Certificate $Certname could not be imported with password. Exception: $_"
        return
    }
    $Thumbprint = $Pfx.Thumbprint
    $Pfx.Dispose()
    return $Thumbprint
}

<#
.SYNOPSIS
    This function checks if the powershell session has been authenticated by the context provided in the CAMConfig
.DESCRIPTION
    This function checks to see if the current powershell session has been authenticated with the same TenantId as specified in the CAMConfig.
.PARAMETER CAMConfig
    (optional) A configuration object used to override the fallback variable and any present configuration files.
.EXAMPLE
    C:\PS> LoggedIn -CAMConfig $CustomConfig
#>

function LoggedIn() {
param(
    [parameter()]
    [PSTypeName("CAMConfig")]$CAMConfig = $script:CAMConfig
)
    $Context = Get-AzureRmContext
    if ($null -ne $Context.Tenant -and $Context.Tenant.Id -eq $CAMConfig.TenantId) {
        return $true
    }
    return $false
}

Export-ModuleMember -Function New-CamConfig

Export-ModuleMember -Function Install-AADAppCertificate
Export-ModuleMember -Function Read-CAMConfig
Export-ModuleMember -Function New-CAMSchedule

Export-ModuleMember -Function Authenticate-WithUserProfile
Export-ModuleMember -Function Authenticate-WithCertificate
Export-ModuleMember -Function Authenticate-WithKey
Export-ModuleMember -Function Authenticate-ToKeyVault

Export-ModuleMember -Function Grant-CertificateAccess

Export-ModuleMember -Function Install-KVCertificates
Export-ModuleMember -Function Install-KVCertificateObject
Export-ModuleMember -Function Install-KVSecretObject
Export-ModuleMember -Function Remove-Certificate