en-US/about_AuthenticodeAzureKeys.help.txt

TOPIC
    about_authenticodeazurekeys
 
SHORT DESCRIPTION
    OpenAuthenticode can use an Azure KeyVault certificate and key to sign data
    without having the key leave the vault. It sends the data that needs to be
    signed, typically a hash, to the Azure API and receives the signature back
    from that operation. This guide will demonstrate how to easily set up an
    Azure App Principal through the Azure CLI that can be used for this
    operation. It assumes that an Azure KeyVault and signing certificate has
    already been created/imported.
 
LONG DESCRIPTION
    The following is a bash script that can be used to generate an Azure App
    Principal that can be used with
    Get-OpenAuthenticodeAzKey
    to sign files. The `APP_NAME`, `RESOURCE_GROUP`, `VAULT_NAME`, and
    `VAULT_CERT` variables need to be prefilled before running the code. It also
    requires the Azure CLI to be installed, otherwise run this in `docker run
    -it mcr.microsoft.com/azure-cli` where the cli is already available.
 
    # The name of the app principal granted access
    APP_NAME="..."
     
    # The name of the resource group the vault is stored in
    RESOURCE_GROUP="..."
     
    # The name of the Azure KeyVault.
    VAULT_NAME="..."
     
    # The name of the certificate in the above vault to use for signing
    VAULT_CERT="..."
     
    ROLE_NAME="KeyVault PowerShell-OpenAuthenticode"
     
    SUBSCRIPTION_ID="$( az login | jq -r '.[].id' )"
     
    ROLE_INFO="$(
        az role definition list \
            --name "${ROLE_NAME}" \
            --custom-role-only \
            --resource-group "${RESOURCE_GROUP}"
    )"
     
    if [ "${ROLE_INFO}" == "[]" ]; then
        ROLE_DEF="$(cat << EOF
    {
        "Name": "${ROLE_NAME}",
        "Description": "Allow access to a cert for Authenticode signing with PowerShell-OpenAuthenticode.",
        "Actions": [],
        "DataActions": [
            "Microsoft.KeyVault/vaults/certificates/read",
            "Microsoft.KeyVault/vaults/keys/sign/action"
        ],
        "NotDataActions": [],
        "AssignableScopes": ["/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}"]
    }
    EOF
    )"
     
        ROLE_INFO="$(
            az role definition create \
                --role-definition "${ROLE_DEF}"
        )"
        ROLE_ID="$(
            echo "${ROLE_INFO}" |
            jq -r '.name'
        )"
    else
        ROLE_ID="$(
            echo "${ROLE_INFO}" |
            jq -r '.[].name'
        )"
    fi
     
    PRINCIPAL_INFO="$(
        az ad sp create-for-rbac \
            --name "${APP_NAME}"
    )"
     
    AZURE_CREDENTIALS="$(
        echo "${PRINCIPAL_INFO}" |
        jq -r "{AZURE_CLIENT_ID: .appId, AZURE_CLIENT_SECRET: .password, AZURE_TENANT_ID: .tenant, AZURE_SUBSCRIPTION_ID: \"${SUBSCRIPTION_ID}\", AZURE_VAULT_NAME: \"${VAULT_NAME}\", AZURE_VAULT_CERT: \"${VAULT_CERT}\"}"
    )"
    CLIENT_ID="$(
        echo "${PRINCIPAL_INFO}" |
        jq -r '.appId'
    )"
     
    az role assignment create \
        --assignee "${CLIENT_ID}" \
        --role "${ROLE_ID}" \
        --scope "/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}/providers/Microsoft.KeyVault/vaults/${VAULT_NAME}" > /dev/null
     
    echo "These details should be saved as a secret where needed"
    echo "${AZURE_CREDENTIALS}"
 
    The resulting json contains all the information needed to sign files using
    OpenAuthenticode.
 
    # AZURE_CREDENTIALS contains the json from the above script
    $credInfo = ConvertFrom-Json -InputObject $env:AZURE_CREDENTIALS
    $vaultName = $credInfo.AZURE_VAULT_NAME
    $vaultCert = $credInfo.AZURE_VAULT_CERT
     
    $env:AZURE_CLIENT_ID = $credInfo.AZURE_CLIENT_ID
    $env:AZURE_CLIENT_SECRET = $credInfo.AZURE_CLIENT_SECRET
    $env:AZURE_TENANT_ID = $credInfo.AZURE_TENANT_ID
    $key = Get-OpenAuthenticodeAzKey -Vault $vaultName -Certificate $vaultCert
    $env:AZURE_CLIENT_ID = ''
    $env:AZURE_CLIENT_SECRET = ''
    $env:AZURE_TENANT_ID = ''
     
    $signParams = @{
        Key = $key
        TimeStampServer = 'http://timestamp.digicert.com'
        HashAlgorithm = 'SHA256'
    }
    Set-OpenAuthenticodeSignature -FilePath $path @signParams
 
    In this example the output json from the bash script has been stored in the
    environment variable `AZURE_CREDENTIALS`. Ensure the credentials json is
    stored securely so that it cannot be used for any unauthorised signing operations.
    If using GitHub Actions it is possible to not need the `AZURE_CLIENT_SECRET`
    and use
    Open ID Connect
    for authentication. It is highly recommended to use `OIDC` when available as
    the client secret does not need to be stored in the repo. GitHub will use
    `OIDC` to generate a short lived authentication token using the grants given
    to the service principal. The following code can be used to add a federated
    credential that gives access to a GitHub Action workflow running on the
    `main` branch of the repo.
 
    # The name of the app principal to add the federated credential for
    APP_NAME="..."
     
    # The GitHub username the repo is in
    GH_USER="..."
     
    # The GitHub repo name for the user specified
    GH_REPO="..."
     
    # The GitHub repo branch to grant access to
    GH_BRANCH="main"
     
    OBJECT_ID="$(
        az ad app list \
            --display-name "${APP_NAME}" |
        jq -r '.[].id'
    )"
     
    az ad app federated-credential create \
        --id "${OBJECT_ID}" \
        --parameters @- << EOF
    {
        "name": "OpenAuthenticode-${GH_USER}-${GH_REPO}-Branch-${GH_BRANCH}",
        "issuer": "https://token.actions.githubusercontent.com",
        "subject": "repo:${GH_USER}/${GH_REPO}:ref:refs/heads/${GH_BRANCH}",
        "description": "GitHub Actions OpenAuthenticode for git@github.com:${GH_USER}/${GH_REPO} refs/heads/${GH_BRANCH}",
        "audiences": [
            "api://AzureADTokenExchange"
        ]
    }
    EOF
 
    It is possible to setup a federated credential with a tag, environment, or
    pull request, see
    GitHub Actions federated identity
    for more details. There is currently a limit of 20 federated credentials per
    principal, simply create a new principal if this limit is reached. Once the
    federated credential has been created it can be used in GitHub Actions like
    the following:
 
    ...
     
    jobs:
      build:
        name: build
        runs-on: ubuntu-latest
        permissions:
          # Needed for Azure OIDC authentication
          id-token: write
          # Needed to checkout repository
          contents: read
     
        steps:
        - name: Check out repository
          uses: actions/checkout@v3
     
        - name: OIDC Login to Azure
          if: github.ref == 'refs/heads/main'
          uses: azure/login@v1
          with:
            client-id: ${{ secrets.AZURE_CLIENT_ID }}
            tenant-id: ${{ secrets.AZURE_TENANT_ID }}
            subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
     
        - name: Sign module
          if: github.ref == 'refs/heads/main'
          shell: pwsh
          run: |
            Install-Module -Name OpenAuthenticode -Force
            Import-Module OpenAuthenticode
     
            $keyParams = @{
                VaultName = '${{ secrets.AZURE_VAULT_NAME }}'
                Certificate = '${{ secrets.AZURE_VAULT_CERT_NAME }}'
            }
            $key = Get-OpenAuthenticodeAzKey @keyParams
     
            $signParams = @{
                Key = $key
                TimeStampServer = 'http://timestamp.digicert.com'
                Verbose = $true
            }
            Set-OpenAuthenticodeSignature @signParams -FilePath '...'
    ...
 
    The claim generated ensures only runs for commits to the `main` branch of
    that repo can authenticate with Azure. See
    TestAzureCodeOIDC
    for a full example of how it can be integrated.
If the
    Az.Accounts
    module is installed it can be used to authenticate with Azure using the
    parameters it exposes. Once authenticated the `Get-OpenAuthenticodeAzKey`
    cmdlet will reuse that authenticated context when retrieving the key.
 
    $credInfo = ConvertFrom-Json -InputObject $env:AZURE_CREDENTIALS
    $vaultName = $credInfo.AZURE_VAULT_NAME
    $vaultCert = $credInfo.AZURE_VAULT_CERT
    $cred = ... # Left up to the reader to build
     
    $connectParams = @{
        TenantId = $credInfo.AZURE_VAULT_TENANT_ID
        ServicePrincipal = $true
        Credential = $cred
    }
    Connect-AzAccount @connectParams
     
    $key = Get-OpenAuthenticodeAzKey -Vault $vaultName -Certificate $vaultCert
    $signParams = @{
        Key = $key
        TimeStampServer = 'http://timestamp.digicert.com'
        HashAlgorithm = 'SHA256'
    }
    Set-OpenAuthenticodeSignature -FilePath $path @signParams