change the Web UI certificate via the API.

Started by jjrushford, November 15, 2025, 12:19:24 AM

Previous topic - Next topic
Greetings,

I run acme scripts outside of OpnSense to get and update a wildcard certificate for my domain.  I use this certificate for various services and I automate their installation via the acme post installation hooks.

I have a tool that lets me import the certificate to OpnSense using the API but, I cannot find the endpoint and required parameters to update the Administration settings to use the newly imported certificate in the Web UI.  Is there an API endpoint to do this?  I'm running OPNsense 25.1.12

thanks
John

Hi

I am looking for that too. Unfortunately I still have no answer.
According to the API reference(here: OPNsense API Reference), there should be the action "set", which my intuition says that it's "setting a certificate to a certain status/value". AI suggested that, in order to update an existing certificate, I should send POST request identical as the "add" one, but simply send it to https://<my_opnsense>/api/trust/cert/set/<uuid_of_the_existing_cert> . I've tried that, and many other combination like uuid field in the body or in the URL in a PHP fashion(?uuid=<uuid_of_the_existing_cert>), but nothing worked.

I am out of clues after exhausting all I could get from google and AI

What I do is just replace the existing one via the api and restart the webgui.  I think it matches on description.

I have scripts to do exactly this, but I can't share them until I'm back at work in January.
Hardware:
DEC750

Hi, thanks. I'll remind you about it :)


may I ask, are your scripts using API only, or you're doing it the PHP way?

Thx.

McCasian

Powershell scripts. Let's see how this looks

param($result)

# ----- EDIT THESE -----
$FullchainPath = '/usr/share/certify/alanplum.crt'
$PrivKeyPath   = '/usr/share/certify/alanplum.key'
$DescrCommon   = 'CertifyTheWeb Wildcard'

$Targets = @(
  @{ hostName='hide.me.net'; key='hidden'; secret='hidden' }
)
# --------------------------------

# Ensure PowerShell 7+ for -SkipCertificateCheck
if (-not ($PSVersionTable.PSVersion.Major -ge 7)) {
  throw "This script requires PowerShell 7+ for -SkipCertificateCheck. Current: $($PSVersionTable.PSVersion)"
}

# Read PEMs once; use LEAF cert
if (!(Test-Path $FullchainPath)) { throw "Fullchain not found: $FullchainPath" }
if (!(Test-Path $PrivKeyPath))   { throw "Private key not found: $PrivKeyPath" }

$allPem  = (Get-Content -Raw $FullchainPath) -replace "`r`n","`n"
$LeafPem = [regex]::Match(
  $allPem,
  '-----BEGIN CERTIFICATE-----(?:.|\n)+?-----END CERTIFICATE-----'
).Value

$KeyPem  = (Get-Content -Raw $PrivKeyPath) -replace "`r`n","`n"

function Invoke-OpnAddOrUpdate {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)][string]$HostName,
    [Parameter(Mandatory)][string]$Key,
    [Parameter(Mandatory)][string]$Secret,
    [Parameter(Mandatory)][string]$Descr,
    [Parameter(Mandatory)][string]$Leaf,
    [Parameter(Mandatory)][string]$Prv
  )

  $base   = "https://$HostName"
  $pair   = "${Key}:${Secret}"
  $basic  = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($pair))
  $headers = @{
    Authorization = $basic
    Accept        = 'application/json'
  }

  # 1) Search existing by description
  $uuid = $null
  try {
    $search = Invoke-RestMethod -Method Get -Uri "$base/api/trust/cert/search" `
              -Headers $headers -SkipCertificateCheck
    if ($search -and $search.rows) {
      $row = $search.rows | Where-Object { $_.descr -eq $Descr } | Select-Object -First 1
      if ($row) { $uuid = $row.uuid }
    }
  } catch {
    $status = $_.Exception.Response.StatusCode.value__ 2>$null
    $msg    = if ($_.ErrorDetails.Message) { $_.ErrorDetails.Message } else { $_.Exception.Message }
    Write-Warning ("[FAIL] {0} search (HTTP {1}) {2}" -f $HostName,$status,$msg)
    return
  }

  # 2) Build payload (PEMs via *_payload)
  $payload = @{
    cert = @{
      action               = 'import'
      descr                = $Descr
      cert_type            = 'usr_cert'
      private_key_location = 'firewall'
      crt_payload          = $Leaf
      prv_payload          = $Prv
      csr_payload          = ''
    }
  } | ConvertTo-Json -Depth 8

  # 3) Update if found, else add
  if ($uuid) { $url = "$base/api/trust/cert/set/$uuid"; $action='UPDATED' }
  else       { $url = "$base/api/trust/cert/add"      ; $action='ADDED'  }

  try {
    $null = Invoke-RestMethod -Method Post -Uri $url -Headers $headers `
              -ContentType 'application/json' -Body $payload -SkipCertificateCheck
    Write-Host ("[OK]   {0} {1}" -f $HostName,$action)
  } catch {
    $status = $_.Exception.Response.StatusCode.value__ 2>$null
    $msg    = if ($_.ErrorDetails.Message) { $_.ErrorDetails.Message } else { $_.Exception.Message }
    Write-Warning ("[FAIL] {0} (HTTP {1}) {2}" -f $HostName,$status,$msg)
  }
}

foreach ($t in $Targets) {
  try {
    $descr = if ($t.ContainsKey('descr') -and $t.descr) { $t.descr } else { $DescrCommon }
    Invoke-OpnAddOrUpdate -HostName $t.hostName -Key $t.key -Secret $t.secret `
                          -Descr $descr -Leaf $LeafPem -Prv $KeyPem
  } catch {
    Write-Warning ("[FAIL] {0} unexpected error: {1}" -f $t.hostName, $_.Exception.Message)
  }
}

That's was hard to get from my iPad at home :)

Hardware:
DEC750