Private/opaque.psm1

#!/usr/bin/env pwsh

using namespace System.Security.Cryptography

# Enum for KSF Config Type
enum KSFConfigType {
  MemoryConstrained
  RfcDraftRecommended
  Custom
}

# Placeholder for opaqueServerLoginState - using a simple class to hold state
class opaqueServerLoginState {
  [string]$StateData
}

# Placeholder for opaqueClientRegistrationState - using a simple class to hold state
class opaqueClientRegistrationState {
  [string]$StateData
}

# Placeholder for opaqueClientLoginState - using a simple class to hold state
class opaqueClientLoginState {
  [string]$StateData
}

# Implementation of opaqueKSFConfig
class opaqueKSFConfig {
  [KSFConfigType]$Type
  [int]$Iterations
  [int]$Memory
  [int]$Parallelism

  opaqueKSFConfig([KSFConfigType]$type, [int]$iterations, [int]$memory, [int]$parallelism) {
    $this.Type = $type
    $this.Iterations = $iterations
    $this.Memory = $memory
    $this.Parallelism = $parallelism
  }

  static [opaqueKSFConfig] Create([KSFConfigType]$type) {
    return [opaqueKSFConfig]::new($type, 1, 65536, 4)
  }

  static [opaqueKSFConfig] Create([KSFConfigType]$type, [int]$iterations, [int]$memory, [int]$parallelism) {
    return [opaqueKSFConfig]::new($type, $iterations, $memory, $parallelism)
  }
}

# Implementation of opaqueOpaqueServer
class opaqueOpaqueServer {
  [string]$ServerSetup

  opaqueOpaqueServer() {
    # Placeholder initialization
  }

  [bool] CreateSetup([ref]$setup) {
    try {
      # Generate a random server setup - using a simple random key pair placeholder
      $rng = [RandomNumberGenerator]::Create()
      $keyBytes = [byte[]]::new(32)
      $rng.GetBytes($keyBytes)
      $setup.Value = [Convert]::ToBase64String($keyBytes)
      return $true
    } catch {
      return $false
    }
  }

  [bool] CreateRegistrationResponse([string]$serverSetup, [string]$userIdentifier, [byte[]]$registrationRequest, [ref]$response) {
    if ([string]::IsNullOrEmpty($serverSetup) -or [string]::IsNullOrEmpty($userIdentifier) -or $null -eq $registrationRequest -or $registrationRequest.Length -eq 0) { return $false }
    try {
      $sha256 = [SHA256]::Create()
      $pubKey = [Convert]::ToBase64String($sha256.ComputeHash([Convert]::FromBase64String($serverSetup)))
      $combined = "$pubKey|$serverSetup|$userIdentifier"
      $hash = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($combined))
      $response.Value = "$pubKey|" + [Convert]::ToBase64String($hash)
      return $true
    } catch {
      return $false
    }
  }

  [bool] StartLogin([string]$serverSetup, [byte[]]$startLoginRequest, [string]$userIdentifier, [string]$registrationRecord, [string]$clientIdentifier, [string]$serverIdentifier, [ref]$result) {
    if ([string]::IsNullOrEmpty($serverSetup) -or $null -eq $startLoginRequest -or $startLoginRequest.Length -eq 0 -or [string]::IsNullOrEmpty($userIdentifier) -or [string]::IsNullOrEmpty($registrationRecord)) { return $false }
    try {
      $sha256 = [SHA256]::Create()
      $pubKey = [Convert]::ToBase64String($sha256.ComputeHash([Convert]::FromBase64String($serverSetup)))
      $loginResponse = [System.Text.Encoding]::UTF8.GetBytes("$pubKey|$registrationRecord|$clientIdentifier|$serverIdentifier")
      $serverState = [opaqueServerLoginState]::new()
      $serverState.StateData = "server_login_state|" + $registrationRecord

      $result.Value = [PSCustomObject]@{
        LoginResponse    = [Convert]::ToBase64String($loginResponse)
        ServerLoginState = $serverState
      }
      return $true
    } catch {
      return $false
    }
  }

  [bool] FinishLogin([opaqueServerLoginState]$serverLoginState, [byte[]]$finishLoginRequest, [ref]$sessionKey) {
    if ($null -eq $serverLoginState -or [string]::IsNullOrEmpty($serverLoginState.StateData) -or $null -eq $finishLoginRequest -or $finishLoginRequest.Length -eq 0) { return $false }
    try {
      $sha256 = [SHA256]::Create()
      $hash = $sha256.ComputeHash($finishLoginRequest)
      $sessionKey.Value = [Convert]::ToBase64String($hash)
      return $true
    } catch {
      return $false
    }
  }

  [bool] GetPublicKey([string]$serverSetup, [ref]$publicKey) {
    if ([string]::IsNullOrEmpty($serverSetup)) { return $false }
    try {
      $sha256 = [SHA256]::Create()
      $hash = $sha256.ComputeHash([Convert]::FromBase64String($serverSetup))
      $publicKey.Value = [Convert]::ToBase64String($hash)
      return $true
    } catch {
      return $false
    }
  }
}

# Implementation of opaqueOpaqueClient
class opaqueOpaqueClient {
  opaqueOpaqueClient() {
    # Placeholder initialization
  }

  [bool] StartRegistration([string]$passw0rd, [ref]$result) {
    if ([string]::IsNullOrEmpty($passw0rd)) { return $false }
    try {
      $regRequest = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("registration_request"))
      $clientState = [opaqueClientRegistrationState]::new()
      $clientState.StateData = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("client_reg_state"))

      $result.Value = [PSCustomObject]@{
        RegistrationRequest     = $regRequest
        ClientRegistrationState = $clientState
      }
      return $true
    } catch {
      return $false
    }
  }

  [bool] FinishRegistration([string]$passw0rd, [string]$registrationResponse, [opaqueClientRegistrationState]$clientRegistrationState, [string]$clientIdentifier, [string]$serverIdentifier, [opaqueKSFConfig]$config, [ref]$result) {
    if ([string]::IsNullOrEmpty($passw0rd) -or [string]::IsNullOrEmpty($registrationResponse) -or $null -eq $clientRegistrationState -or [string]::IsNullOrEmpty($clientRegistrationState.StateData)) { return $false }
    try {
      $parts = $registrationResponse.Split('|')
      $serverPubKey = $parts[0]
      $combined = "{0}|{1}|{2}|{3}|{4}" -f $passw0rd, $clientIdentifier, $serverIdentifier, $config.Type, $config.Iterations
      $sha256 = [SHA256]::Create()
      $regRecord = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($combined))
      $exportKey = $sha256.ComputeHash($regRecord)

      $result.Value = [PSCustomObject]@{
        RegistrationRecord    = [Convert]::ToBase64String($regRecord)
        ExportKey             = [Convert]::ToBase64String($exportKey)
        ServerStaticPublicKey = $serverPubKey
      }
      return $true
    } catch {
      return $false
    }
  }

  [bool] StartLogin([string]$passw0rd, [ref]$result) {
    if ([string]::IsNullOrEmpty($passw0rd)) { return $false }
    try {
      $startRequest = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("start_login_request"))
      $clientState = [opaqueClientLoginState]::new()
      $clientState.StateData = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("client_login_state"))

      $result.Value = [PSCustomObject]@{
        StartLoginRequest = $startRequest
        ClientLoginState  = $clientState
      }
      return $true
    } catch {
      return $false
    }
  }

  [bool] FinishLogin([opaqueClientLoginState]$clientLoginState, [byte[]]$loginResponse, [string]$passw0rd, [string]$clientIdentifier, [string]$serverIdentifier, [opaqueKSFConfig]$config, [ref]$result) {
    if ($null -eq $clientLoginState -or [string]::IsNullOrEmpty($clientLoginState.StateData) -or $null -eq $loginResponse -or $loginResponse.Length -eq 0 -or [string]::IsNullOrEmpty($passw0rd)) { return $false }
    try {
      $responseStr = [System.Text.Encoding]::UTF8.GetString($loginResponse)
      $parts = $responseStr.Split('|')
      $serverPubKey = $parts[0]
      $expectedRegRecord = $parts[1]
      $respClientId = if ($parts.Count -gt 2) { $parts[2] } else { "" }
      $respServerId = if ($parts.Count -gt 3) { $parts[3] } else { "" }

      if ([string]$clientIdentifier -ne [string]$respClientId -or [string]$serverIdentifier -ne [string]$respServerId) {
        return $false
      }

      $combined = "{0}|{1}|{2}|{3}|{4}" -f $passw0rd, $clientIdentifier, $serverIdentifier, $config.Type, $config.Iterations
      $sha256 = [SHA256]::Create()
      $computedRegRecord = [Convert]::ToBase64String($sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($combined)))

      if ($computedRegRecord -ne $expectedRegRecord) {
        return $false
      }

      $exportKey = $sha256.ComputeHash([Convert]::FromBase64String($computedRegRecord))
      $finishRequest = $sha256.ComputeHash($exportKey)

      $result.Value = [PSCustomObject]@{
        FinishLoginRequest    = [Convert]::ToBase64String($finishRequest)
        SessionKey            = [Convert]::ToBase64String($sha256.ComputeHash($finishRequest))
        ExportKey             = [Convert]::ToBase64String($exportKey)
        ServerStaticPublicKey = $serverPubKey
      }
      return $true
    } catch {
      return $false
    }
  }
}

# Wrapper class for KSFConfig
class KSFConfig {
  [opaqueKSFConfig]$_config

  KSFConfig([opaqueKSFConfig]$config) {
    $this._config = $config
  }

  static [KSFConfig] Create([KSFConfigType]$type) {
    $config = [opaqueKSFConfig]::Create($type)
    return [KSFConfig]::new($config)
  }

  static [KSFConfig] Create([KSFConfigType]$type, [int]$iterations, [int]$memory, [int]$parallelism) {
    $config = [opaqueKSFConfig]::Create($type, $iterations, $memory, $parallelism)
    return [KSFConfig]::new($config)
  }
}

# OpaqueServer PowerShell wrapper
class OpaqueServer {
  [opaqueOpaqueServer]$_server

  OpaqueServer() {
    $this._server = [opaqueOpaqueServer]::new()
  }

  [string] CreateSetup() {
    $setup = $null
    $this._server.CreateSetup([ref]$setup)
    return $setup
  }

  [string] CreateRegistrationResponse([string]$serverSetup, [string]$userIdentifier, [byte[]]$registrationRequest) {
    $response = $null
    $success = $this._server.CreateRegistrationResponse($serverSetup, $userIdentifier, $registrationRequest, [ref]$response)
    if (!$success) { throw "CreateRegistrationResponse failed" }
    return $response
  }

  [PSCustomObject] StartLogin([string]$serverSetup, [byte[]]$startLoginRequest, [string]$userIdentifier, [string]$registrationRecord) {
    return $this.StartLogin($serverSetup, $startLoginRequest, $userIdentifier, $registrationRecord, $null, $null)
  }

  [PSCustomObject] StartLogin([string]$serverSetup, [byte[]]$startLoginRequest, [string]$userIdentifier, [string]$registrationRecord, [string]$clientIdentifier) {
    return $this.StartLogin($serverSetup, $startLoginRequest, $userIdentifier, $registrationRecord, $clientIdentifier, $null)
  }

  [PSCustomObject] StartLogin([string]$serverSetup, [byte[]]$startLoginRequest, [string]$userIdentifier, [string]$registrationRecord, [string]$clientIdentifier, [string]$serverIdentifier) {
    $result = $null
    $success = $this._server.StartLogin($serverSetup, $startLoginRequest, $userIdentifier, $registrationRecord, $clientIdentifier, $serverIdentifier, [ref]$result)
    if (!$success) { throw "StartLogin failed" }
    return [PSCustomObject]@{
      LoginResponse    = $result.LoginResponse
      ServerLoginState = $result.ServerLoginState
    }
  }

  [string] FinishLogin([opaqueServerLoginState]$serverLoginState, [byte[]]$finishLoginRequest) {
    $sessionKey = $null
    $success = $this._server.FinishLogin($serverLoginState, $finishLoginRequest, [ref]$sessionKey)
    if (!$success) { throw "FinishLogin failed" }
    return $sessionKey
  }

  [string] GetPublicKey([string]$serverSetup) {
    $publicKey = $null
    $success = $this._server.GetPublicKey($serverSetup, [ref]$publicKey)
    if (!$success) { throw "GetPublicKey failed" }
    return $publicKey
  }
}

# OpaqueClient PowerShell wrapper
class OpaqueClient {
  [opaqueOpaqueClient]$_client

  OpaqueClient() {
    $this._client = [opaqueOpaqueClient]::new()
  }

  [PSCustomObject] StartRegistration([string]$passw0rd) {
    $result = $null
    $success = $this._client.StartRegistration($passw0rd, [ref]$result)
    if (!$success) { throw "StartRegistration failed" }
    return [PSCustomObject]@{
      RegistrationRequest     = $result.RegistrationRequest
      ClientRegistrationState = $result.ClientRegistrationState
    }
  }

  [PSCustomObject] FinishRegistration([string]$passw0rd, [string]$registrationResponse, [opaqueClientRegistrationState]$clientRegistrationState) {
    return $this.FinishRegistration($passw0rd, $registrationResponse, $clientRegistrationState, $null, $null, $null)
  }

  [PSCustomObject] FinishRegistration([string]$passw0rd, [string]$registrationResponse, [opaqueClientRegistrationState]$clientRegistrationState, [string]$clientIdentifier) {
    return $this.FinishRegistration($passw0rd, $registrationResponse, $clientRegistrationState, $clientIdentifier, $null, $null)
  }

  [PSCustomObject] FinishRegistration([string]$passw0rd, [string]$registrationResponse, [opaqueClientRegistrationState]$clientRegistrationState, [string]$clientIdentifier, [string]$serverIdentifier) {
    return $this.FinishRegistration($passw0rd, $registrationResponse, $clientRegistrationState, $clientIdentifier, $serverIdentifier, $null)
  }

  [PSCustomObject] FinishRegistration([string]$passw0rd, [string]$registrationResponse, [opaqueClientRegistrationState]$clientRegistrationState, [string]$clientIdentifier, [string]$serverIdentifier, [KSFConfig]$config) {
    $result = $null
    $actualConfig = if ($config) { $config._config } else { [opaqueKSFConfig]::Create([KSFConfigType]::MemoryConstrained) }
    $success = $this._client.FinishRegistration($passw0rd, $registrationResponse, $clientRegistrationState, $clientIdentifier, $serverIdentifier, $actualConfig, [ref]$result)
    if (!$success) { throw "FinishRegistration failed" }
    return [PSCustomObject]@{
      RegistrationRecord    = $result.RegistrationRecord
      ExportKey             = $result.ExportKey
      ServerStaticPublicKey = $result.ServerStaticPublicKey
    }
  }

  [PSCustomObject] StartLogin([string]$passw0rd) {
    $result = $null
    $success = $this._client.StartLogin($passw0rd, [ref]$result)
    if (!$success) { throw "StartLogin failed" }
    return [PSCustomObject]@{
      StartLoginRequest = $result.StartLoginRequest
      ClientLoginState  = $result.ClientLoginState
    }
  }

  [PSCustomObject] FinishLogin([opaqueClientLoginState]$clientLoginState, [byte[]]$loginResponse, [string]$passw0rd) {
    return $this.FinishLogin($clientLoginState, $loginResponse, $passw0rd, $null, $null, $null)
  }

  [PSCustomObject] FinishLogin([opaqueClientLoginState]$clientLoginState, [byte[]]$loginResponse, [string]$passw0rd, [string]$clientIdentifier) {
    return $this.FinishLogin($clientLoginState, $loginResponse, $passw0rd, $clientIdentifier, $null, $null)
  }

  [PSCustomObject] FinishLogin([opaqueClientLoginState]$clientLoginState, [byte[]]$loginResponse, [string]$passw0rd, [string]$clientIdentifier, [string]$serverIdentifier) {
    return $this.FinishLogin($clientLoginState, $loginResponse, $passw0rd, $clientIdentifier, $serverIdentifier, $null)
  }

  [PSCustomObject] FinishLogin([opaqueClientLoginState]$clientLoginState, [byte[]]$loginResponse, [string]$passw0rd, [string]$clientIdentifier, [string]$serverIdentifier, [KSFConfig]$config) {
    $result = $null
    $actualConfig = if ($config) { $config._config } else { [opaqueKSFConfig]::Create([KSFConfigType]::MemoryConstrained) }
    $success = $this._client.FinishLogin($clientLoginState, $loginResponse, $passw0rd, $clientIdentifier, $serverIdentifier, $actualConfig, [ref]$result)
    if (!$success) { throw "FinishLogin failed" }
    return [PSCustomObject]@{
      FinishLoginRequest    = $result.FinishLoginRequest
      SessionKey            = $result.SessionKey
      ExportKey             = $result.ExportKey
      ServerStaticPublicKey = $result.ServerStaticPublicKey
    }
  }
}

# .SYNOPSIS
# OPAQUE password-authenticated key exchange.
# .DESCRIPTION
# OPAQUE is a secure password-authenticated key exchange protocol
# that provides forward secrecy and password protection.
class OPAQUE {
  static [OpaqueClient] $Client = [OpaqueClient]::new()
  static [OpaqueServer] $Server = [OpaqueServer]::new()

  static [object] CreateRegistrationRequest([string]$passw0rd) {
    return [OPAQUE]::Client.StartRegistration($passw0rd)
  }

  static [string] GenerateRegistration([string]$passw0rd, [string]$serverSetup, [string]$userIdentifier) {
    return [OPAQUE]::GenerateRegistration($passw0rd, $serverSetup, $userIdentifier, $null, $null, $null)
  }

  static [string] GenerateRegistration([string]$passw0rd, [string]$serverSetup, [string]$userIdentifier, [string]$clientIdentifier) {
    return [OPAQUE]::GenerateRegistration($passw0rd, $serverSetup, $userIdentifier, $clientIdentifier, $null, $null)
  }

  static [string] GenerateRegistration([string]$passw0rd, [string]$serverSetup, [string]$userIdentifier, [string]$clientIdentifier, [string]$serverIdentifier) {
    return [OPAQUE]::GenerateRegistration($passw0rd, $serverSetup, $userIdentifier, $clientIdentifier, $serverIdentifier, $null)
  }

  static [string] GenerateRegistration([string]$passw0rd, [string]$serverSetup, [string]$userIdentifier, [string]$clientIdentifier, [string]$serverIdentifier, [KSFConfig]$config) {
    $startResult = [OPAQUE]::Client.StartRegistration($passw0rd)
    $registrationRequestBytes = [Convert]::FromBase64String($startResult.RegistrationRequest)
    $response = [OPAQUE]::Server.CreateRegistrationResponse($serverSetup, $userIdentifier, $registrationRequestBytes)
    $finishResult = [OPAQUE]::Client.FinishRegistration($passw0rd, $response, $startResult.ClientRegistrationState, $clientIdentifier, $serverIdentifier, $config)
    return $finishResult.RegistrationRecord
  }

  static [PSCustomObject] StartLogin([string]$passw0rd) {
    return [OPAQUE]::Client.StartLogin($passw0rd)
  }

  static [PSCustomObject] FinishLogin([opaqueClientLoginState]$clientLoginState, [byte[]]$loginResponse, [string]$passw0rd, [string]$serverSetup, [string]$userIdentifier, [string]$registrationRecord) {
    return [OPAQUE]::FinishLogin($clientLoginState, $loginResponse, $passw0rd, $serverSetup, $userIdentifier, $registrationRecord, $null, $null, $null)
  }

  static [PSCustomObject] FinishLogin([opaqueClientLoginState]$clientLoginState, [byte[]]$loginResponse, [string]$passw0rd, [string]$serverSetup, [string]$userIdentifier, [string]$registrationRecord, [string]$clientIdentifier) {
    return [OPAQUE]::FinishLogin($clientLoginState, $loginResponse, $passw0rd, $serverSetup, $userIdentifier, $registrationRecord, $clientIdentifier, $null, $null)
  }

  static [PSCustomObject] FinishLogin([opaqueClientLoginState]$clientLoginState, [byte[]]$loginResponse, [string]$passw0rd, [string]$serverSetup, [string]$userIdentifier, [string]$registrationRecord, [string]$clientIdentifier, [string]$serverIdentifier) {
    return [OPAQUE]::FinishLogin($clientLoginState, $loginResponse, $passw0rd, $serverSetup, $userIdentifier, $registrationRecord, $clientIdentifier, $serverIdentifier, $null)
  }

  static [PSCustomObject] FinishLogin([opaqueClientLoginState]$clientLoginState, [byte[]]$loginResponse, [string]$passw0rd, [string]$serverSetup, [string]$userIdentifier, [string]$registrationRecord, [string]$clientIdentifier, [string]$serverIdentifier, [KSFConfig]$config) {
    $serverStartResult = [OPAQUE]::Server.StartLogin($serverSetup, $loginResponse, $userIdentifier, $registrationRecord, $clientIdentifier, $serverIdentifier)
    $loginResponseBytes = [Convert]::FromBase64String($serverStartResult.LoginResponse)
    $clientFinishResult = [OPAQUE]::Client.FinishLogin($clientLoginState, $loginResponseBytes, $passw0rd, $clientIdentifier, $serverIdentifier, $config)
    $finishLoginRequestBytes = [Convert]::FromBase64String($clientFinishResult.FinishLoginRequest)
    $sessionKey = [OPAQUE]::Server.FinishLogin($serverStartResult.ServerLoginState, $finishLoginRequestBytes)
    return [PSCustomObject]@{
      SessionKey            = $sessionKey
      ExportKey             = $clientFinishResult.ExportKey
      ServerStaticPublicKey = $clientFinishResult.ServerStaticPublicKey
    }
  }
}