#requires -Version 5.1
[CmdletBinding()]
param(
  [Parameter(Mandatory = $true)]
  [ValidateNotNullOrEmpty()]
  [string]$BackupPfx,

  [Parameter(Mandatory = $true)]
  [SecureString]$Password,

  [ValidateSet(2048, 3072, 4096)]
  [int]$KeySize = 3072,

  [ValidateNotNullOrEmpty()]
  [string]$Subject = 'CN=SmartCut64 Offline Licence Signing',

  [switch]$Force
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

if ($env:OS -ne 'Windows_NT') {
  throw 'SmartCut64 licence-key provisioning requires Windows.'
}

$backupPath = [IO.Path]::GetFullPath($BackupPfx)
if ((Test-Path -LiteralPath $backupPath) -and -not $Force) {
  throw "The private-key backup already exists: $backupPath. Use -Force only after verifying the existing backup."
}
$backupDirectory = [IO.Path]::GetDirectoryName($backupPath)
if (-not [string]::IsNullOrWhiteSpace($backupDirectory)) {
  New-Item -ItemType Directory -Path $backupDirectory -Force | Out-Null
}
$temporaryBackup = [IO.Path]::Combine(
  $backupDirectory,
  '.' + [IO.Path]::GetFileName($backupPath) + '.' + [Guid]::NewGuid().ToString('N') + '.pfx')

$certificate = New-SelfSignedCertificate `
  -Subject $Subject `
  -Type Custom `
  -KeyAlgorithm RSA `
  -KeyLength $KeySize `
  -HashAlgorithm SHA256 `
  -KeyUsage DigitalSignature `
  -KeySpec Signature `
  -KeyExportPolicy ExportableEncrypted `
  -CertStoreLocation 'Cert:\CurrentUser\My' `
  -NotAfter (Get-Date).AddYears(10)

try {
  Export-PfxCertificate -Cert $certificate -FilePath $temporaryBackup -Password $Password `
    -ChainOption EndEntityCertOnly -NoProperties | Out-Null
  if (Test-Path -LiteralPath $backupPath) {
    if (-not $Force) {
      throw "The private-key backup appeared while it was being created: $backupPath"
    }
    [IO.File]::Replace($temporaryBackup, $backupPath, $null)
  }
  else {
    [IO.File]::Move($temporaryBackup, $backupPath)
  }
  $rsa = [Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPublicKey($certificate)
  if ($null -eq $rsa) { throw 'The generated certificate does not contain an RSA public key.' }
  try {
    $parameters = $rsa.ExportParameters($false)
  }
  finally {
    $rsa.Dispose()
  }
  if ($parameters.Modulus.Length * 8 -lt 2048) {
    throw 'The generated RSA key is smaller than 2048 bits.'
  }
  $toHex = {
    param([byte[]]$Bytes)
    -join ($Bytes | ForEach-Object { $_.ToString('x2') })
  }
  $modulus = & $toHex $parameters.Modulus
  $exponent = & $toHex $parameters.Exponent
  [pscustomobject]@{
    CertificateThumbprint = $certificate.Thumbprint
    CertificateStore = 'Cert:\CurrentUser\My'
    PrivateKeyBackup = $backupPath
    PublicModulusHex = $modulus
    PublicExponentHex = $exponent
    CMakeArguments = @(
      "-DSMARTCUT_LICENSE_PUBLIC_MODULUS_HEX=$modulus"
      "-DSMARTCUT_LICENSE_PUBLIC_EXPONENT_HEX=$exponent"
    )
  }
}
catch {
  Write-Warning "The certificate remains in Cert:\CurrentUser\My\$($certificate.Thumbprint)."
  throw
}
finally {
  if (Test-Path -LiteralPath $temporaryBackup) {
    Remove-Item -LiteralPath $temporaryBackup -Force
  }
}
