# bit-keys for Windows
#
# Version 20241127 (0.3)

# Params
param (
    [switch]$cc,
    [switch]$colo,
    [switch]$dev,
    [switch]$flush,
    [switch]$noacl,
    [switch]$help
)

# Variables
$gpg_package_name="GNU Privacy Guard"
$gpg_path = "C:\Program Files (x86)\gnupg\bin"
$ssh_path="C:\ProgramData\ssh"
$ssh_authorized_keys="administrators_authorized_keys"
$bit_keys_dir="$ssh_path/bit-keys"
$bit_keys_noacl="${ssh_path}/bit-keys-no-acl"
$bit_keys_history="${ssh_path}/bit-keys-history"
$gpg_id="7703B420B543BD2DF9AF6197F22B95441BFBE521"
$output_bitkeys = ""
$logname = "BIT-Keys"
$source = "bit-keys"

$gpg_key = @"
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQGNBFuD9iMBDADYPh5/ApKD474Y9lh8UQXi7aZnxS3waF4Yexf7p3P7as3Q9RD8
FwXuwktW84jIwb5yQrGlIHtj5nMTLIErdJYYqaDaq2Lwt/hqYoSoUjXoPxjGSBQ6
P+TyZBPBGSnLoVN/8sDlFVYQ7PtRlNVe0A3SNjCPzpQnBJh9EBJCiJHpfaK0Kmxw
ya1U4bjIggynW2NfYb2pI7dpkTLGEWj6+rreZRu8E6EoIRYNPoAVqBXgAbVZ7F+w
5uZtPYmbJvvVTowAjPeoe0+VkNp1CYKlzmuLIUeHgCOA7v/fopw5Oo77oPkQxH11
VnyFqPN5nOO5505NbnYKJ5jXIXempKE4DCHXFXA5Z3lUv3rOqcdYaTxASlrcG0t9
kJlqdzeGQ9cX1qltsChLemn3Iu9mAAsIGqPd7dJzZ3XM8dTrzpZAEGPKo4qNUnDN
kBJD5K8U9FdBi4mOCQyKtC5P4ncmpzyavOq0i8EeHIS8X58kRo4GR52GWvlB/Vc9
RGkOK/f1JrjSvQcAEQEAAbQZQklUIEtleXMgPHN1cHBvcnRAYml0Lm5sPokBzgQT
AQoAOAIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgBYhBHcDtCC1Q70t+a9hl/Ir
lUQb++UhBQJbhAEiAAoJEPIrlUQb++Uh9w4MAJdyl05IfFqP+eA4hTGetYugN0Hq
4KUx6DNiCv7Eoh/MVwT5lTN4gHR5Ega66d20EOS7lACaXFiTKWS5xHTJEBqk5IiB
Z+Wi2CagB5mZKdsLojR7AJK20vku4cBuoPgR0SoUxXnkPPXF2PtMBcLo29ha8cnb
OmPiT6yJUh3lKGzGJzFyTiSkbprldbzL9teTp0sSSFmdWSud7Pqcqx6bcICQzJZC
XnkyEDbuXaooFpwKtauXJTsa8SYRSMgxIoHHm9eDL3k8Ozvjv/m83+RqidxtDU2p
o9iHpuKQL2fDGJRQdPo7aRsI/SO2z0Nq/nFW5A7wfARBBGA3lNTTgpGLnHEFfsWz
E8eRk8njQj9LOq80/97Wfy/kiwvLQJP4t7vW4pYVfi+hXHMRCFG3ZJl5FetDnvhS
Rfnsj1+vwwGUAN/4y/vUL3SZF6pplLgDcNu7OtotXhRxnmy7HNjFHPCHjnAJia8a
nR7b//ylrbu1q9z/aZnCN045J8ZqiUuHdRSHhrkBjQRbg/YjAQwAvXQ7I12rrlxh
vGztoNTCFtwHveNF7hLASw6ByrC+s8v2CCz2ThrQeIqxAvcWTkVoIGd14AEuhYvd
y8zlpxteBBsDSaMwd0iPOHKiwc4N5ffEz9ZZEA1zanRVynla5w+gf1G3BgGEc+WH
IWoMOigo1m4uJL/1DjAoG7O4wvxt+PtEvgAsBY7ofi8Qt5K95jIj2wc8QwDXVwXi
cpMaykj0dDWuacuJl2uwXGxdpZ9vfffE1zk5XpwgfWIaec8YRNPFxrqfmfcpakyA
PAjRVXMgE8j3jFK8uA2ARxjdjZQg2GQ0Mvq0e/VpoKhvh1xX0hWx4tq64BP095O8
zKowUlVvowhinZAyTxCZs2sS/4cmhB1lBFhZznQGsa9WyHJscqBMqMPi8y/HyWWo
0Qp2KxMmYXZ/wqSk48REOth/RPWjj8omQQ4/UUxmtBMx7+88MEP71YqGc3ngYf/g
VyD59jvzJQew8SVBVCZPE4I2IfMk0GNqcx/ldXp6qVbZaSIrYXevABEBAAGJAbYE
GAEKACACGwwWIQR3A7QgtUO9LfmvYZfyK5VEG/vlIQUCW4QC+QAKCRDyK5VEG/vl
IahpC/4y8oLG8Hb3WEFHvKKLdsDUOHz5NW2/XyZDzLUCFVs6iazWZ1KfeWOwk29+
mlUM28dYQgICPoQBqf9qHf5UvRFy09zPRcp5JCVzSNrXU5BXMwbHHcLklxIAtECy
ZR/YXdq3vIHm0Cto8R/oxJ+sYzfiKF6DhWotBzsGs9UUDnwFdovo5c1PV9dBhKbp
HagnCTfxNrIYkNqSr83lDEYuRS5PP8xFFShiLkhX1Lh94AqhNrXGrAVgQKHv7cn7
OOo+gFDxr+Oztt73DMznFhZT4yVw8sl9n59r59FYkEdoQxx7vlPYsVslj+wdf/Cf
MLE/nT4DnEooovilyF09wUc3ofuNwFVxPSyurBEVAxlsuCIh/m6nkigpynGCm2/u
MVz5ZFCkbLnqpBoCUtKZKZ6JJfdbElve63IcV+rhXwi/nIvjvnoLZi/Po7poMo/A
B64yL313xAqMNoDfcHM+rFvnqVlGXTNNT+kFU1kFxH8LSlijM2oiALrgS3wKjA7k
vg2f4Rg=
=8KrM
-----END PGP PUBLIC KEY BLOCK-----
"@

# Functions
function showHelp {
    $help_message = @"
    Usage: bit-keys.ps1 [OPTIONS]
    Options:
    -cc, -c                 Also place Customer Care SSH keys.
    -colo, -co              Also place Data Center Engineer SSH keys.
    -dev, -d                Also place Research & Development SSH keys.
        Usage of these option are remembered for future runs of bit-keys.

    -flush, -f              Reset the BIT-keys history & ACL choices.

    -no-acl, -n             Do *NOT* put from="" IP-ACL on bit-keys.
        Usage of this option is remembered for future runs of bit-keys.
        By default an IP-ACL is placed on bit-keys. This does *NOT*
        apply to included ".local" files, they get added verbatim.
        Use this option to *NOT* put IP-ACLs on bit-keys.

    -help, -h               Show this help message.
"@

    
    Write-Host "${help_message}"
}

function error_logging {
    param (
        [string]$message
    )

    # Write message to EventViewer and exit
    Write-EventLog -LogName "${logname}" -Source "${source}" -EntryType Error -EventId 5000 -Message "${message}"
}

function info_logging {
    param (
        [string]$message
    )

    # Write message to EventViewer and exit
    Write-EventLog -LogName "${logname}" -Source "${source}" -EntryType Information -EventId 1337 -Message "${message}"
}

function import_gpg_key {
    $gpg_id_exists = gpg --list-keys | Select-String -Pattern ${gpg_id}

    if(!$gpg_id_exists){
        $tempfile = [System.IO.Path]::GetTempFileName()
        $gpg_key | Set-Content -Path ${tempfile}

        gpg -q --import ${tempfile} | Out-Null

        if ($LASTEXITCODE) {
            error_logging "Error while importing GPG key"
            Remove-Item -Path $tempfile -Force | Out-null
            exit 1
        }
    }
}

function get_bit_keys {
    param (
        [string]$package
    )
    
    Invoke-WebRequest -Uri "https://bitkeys.bit.nl/bit-keys/bit-keys-${package}.tar" -OutFile "bit-keys-${package}.tar"
    Invoke-WebRequest -Uri "https://bitkeys.bit.nl/bit-keys/bit-keys-${package}.tar.sig" -OutFile "bit-keys-${package}.tar.sig"

    gpg -q --verify "bit-keys-${package}.tar.sig" "bit-keys-${package}.tar" > $null 2>&1

    if ($LASTEXITCODE -eq 0) {
        tar -xf bit-keys-${package}.tar
        if ($LASTEXITCODE) {
            error_logging "Untar failed?"
            exit 1
        }
    } elseif ($LASTEXITCODE -eq 1) {
        error_logging "Invalid GPG signature for this tarball. Bailing!"
        exit 1
    } elseif ($LASTEXITCODE -eq 2) {
        error_logging "The GPG public key was not found or source file(s) empty. Bailing!"
        exit 1
    } else {
        error_logging "Unhandled GPG error state $LASTEXITCODE. Bailing!"
        exit 1
    }

    ## history
    if (Test-Path "${bit_keys_history}") {
        if (-Not (Select-String -Path "${bit_keys_history}" -Pattern "^${package}$" -Quiet)) {
            Add-Content -Path ${bit_keys_history} -Value "${package}" -Encoding UTF8
        }
    } else {
        Add-Content -Path ${bit_keys_history} -Value "${package}" -Encoding UTF8
    }

}

# Run script
## Create log source
if (-not [System.Diagnostics.EventLog]::SourceExists($source)) {
    New-EventLog -LogName $logname -Source $source
}

## Exit when GPG is not installed
if (-not (Get-Package -Name ${gpg_package_name} -ErrorAction SilentlyContinue)) {
    error_logging "Package ${gpg_package_name} not installed"
    exit 1
} elseif ($env:Path -notlike "*${gpg_path}*") {
    $env:Path += ";${gpg_path}"
}

## Create work directory or cleanup old files
if (-not (Test-Path ${bit_keys_dir})) {
    New-Item -ItemType Directory -Force -Path "${bit_keys_dir}" | Out-Null
} elseif (Get-ChildItem -Path "${bit_keys_dir}") {
    Remove-Item -Path ${bit_keys_dir}\* -Recurse -Force | Out-Null
}

## Parse arguments
if ($help) {
    showHelp
    exit 0
} elseif ($cc) {
    get_bit_keys custcare
} elseif ($colo) {
    get_bit_keys colo
} elseif ($dev) {
    get_bit_keys dev
} elseif ($flush) {
    if (Test-Path ${bit_keys_noacl}) { Remove-Item -Path "${bit_keys_noacl}" -Force | Out-Null }
    if (Test-Path ${bit_keys_history}) { Remove-Item -Path "${bit_keys_history}" -Force | Out-Null }
    info_logging "Flushed history & ACL settings!"
    exit 0
} elseif ($noacl) {
    New-Item -Path ${bit_keys_noacl} -ItemType File -Force | Out-Null
}

## Change directory
Set-Location -Path "${bit_keys_dir}"

## Import GPG Key
import_gpg_key

## Check if there is a history file
if (Test-Path "${bit_keys_history}") {
    $bit_keys_history_content = Get-Content -Path ${bit_keys_history}
    ForEach (${item} in ${bit_keys_history_content}) {
        if ( "${item}" -eq "eng") { continue }
        $output_bitkeys += "Using ${bit_keys_history} for group ${item}`n"
        get_bit_keys ${item}
    }
}

## Download SSH public keys
get_bit_keys eng

## Mangle the trusted_from_addrs into a string for the from="" lines
if (-not (Test-Path "trusted_from_addrs.ps1")) {
    error_logging "The bit-keys tar ball did not have the expected trusted_from_addrs.ps1 file! Bailing."
    exit 1
}

. ./trusted_from_addrs.ps1
$trusted_from_str = ($trusted_from_addrs -join ',')

## Add all bit-keys
$tempfile = [System.IO.Path]::GetTempFileName()
$files = Get-ChildItem -Path . -File -Filter 'id_*.pub.*'
foreach (${file} in ${files}) {
    $name = (${file}.Name -split "\.")[2].Split('_')[-0]
    Get-Content ${file}.FullName | Out-File -Append -FilePath ${tempfile} -Encoding UTF8
    $output_bitkeys += "Added key: $name`n"
}

if (Test-Path "${ssh_path}/authorized_keys.local") {
    Get-Content "${ssh_path}/authorized_keys.local" | Out-File -Append -FilePath ${tempfile} -Encoding UTF8
    $output_bitkeys += "Added keys from ${ssh_path}/authorized_keys.local`n"
}

## Prepend from="" IP-ACL limits to all bit-keys unless disabled.
if (Test-Path "${bit_keys_noacl}"){
    $output_bitkeys += "Found '${bit_keys_noacl}', not enforcing IP-ACLs"
}else{
    $tempfile_content = Get-Content -Path ${tempfile}
    $tempfile_content = ${tempfile_content} | ForEach-Object { "from=`"${trusted_from_str}`" $_" }
    Set-Content -Path ${tempfile} -Value ${tempfile_content} -Encoding UTF8
}


## Update the auth_file
if (Test-Path "${ssh_path}/${ssh_authorized_keys}") {
    Remove-Item -Path "${ssh_path}/${ssh_authorized_keys}" -Force | Out-Null
}
$ssh_authorized_keys_message = @"
# Updated: $(Get-Date -Format "dd-MM-yyyy HH:mm:ss")
#
# DO NOT CHANGE THIS FILE - IT IS MANAGED BY BIT-KEYS!
# ADD YOUR LOCAL SSH PUBLIC KEYS TO A FILE NAMED
# authorized_keys.local AND IT WILL BE INCLUDED IN
# THIS FILE BY BIT-KEYS!
#
"@
${ssh_authorized_keys_message} | Out-File -Append -FilePath "${ssh_path}/${ssh_authorized_keys}" -Encoding UTF8
Get-Content "${tempfile}" | Out-File -Append -FilePath "${ssh_path}/${ssh_authorized_keys}" -Encoding UTF8
info_logging "${output_bitkeys}"

## Cleanup
Remove-Item -Path $tempFile | Out-Null
if (Get-ChildItem -Path "${bit_keys_dir}") {
    Remove-Item -Path ${bit_keys_dir}\* -Recurse -Force | Out-Null
}
