Private/Invoke-ACME.ps1
function Invoke-ACME { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [string]$Uri, [Parameter(Mandatory)] [Security.Cryptography.AsymmetricAlgorithm]$Key, [Parameter(Mandatory)] [hashtable]$Header, [Parameter(Mandatory)] [string]$PayloadJson, [switch]$NoRetry ) # make sure we have a server configured if (!(Get-PAServer)) { throw "No ACME server configured. Run Set-PAServer first." } # Because we're not refreshing the server on module load, we may not have a # NextNonce set yet. So check the header, and grab a fresh one if it's empty. if ([string]::IsNullOrWhiteSpace($Header.nonce)) { $Header.nonce = Get-Nonce } # Validation on the rest of the header will be taken care of by New-Jws. And # the only reason we aren't just simplifying by changing the input param to a # completed JWS string is because we want to be able to auto-retry on errors # like badNonce which requires modifying the Header and re-signing a new JWS. $Jws = New-Jws $Key $Header $PayloadJson # since HTTP error codes make Invoke-WebRequest throw an exception, # we need to wrap it in a try/catch. But we can still get the response # object via the exception. try { $response = Invoke-WebRequest -Uri $Uri -Body $Jws -Method Post ` -ContentType 'application/jose+json' -UserAgent $script:USER_AGENT ` -Headers $script:COMMON_HEADERS -EA Stop @script:UseBasic # update the next nonce if it was sent if ($response.Headers.ContainsKey($script:HEADER_NONCE)) { Write-Debug "Updating nonce: $($response.Headers[$script:HEADER_NONCE])" $script:Dir.nonce = $response.Headers[$script:HEADER_NONCE] } return $response } catch [Net.WebException] { $ex = $_.Exception $response = $ex.Response # update the next nonce if it was sent if ($script:HEADER_NONCE -in $response.Headers) { Write-Debug "Updating nonce from error response: $($response.Headers[$script:HEADER_NONCE])" $script:Dir.nonce = $response.GetResponseHeader($script:HEADER_NONCE) $freshNonce = $true } # ACME uses RFC7807, Problem Details for HTTP APIs # https://tools.ietf.org/html/rfc7807 # So a JSON parseable error object should be in the response body. # We just have to pull it out and parse it. $sr = New-Object IO.StreamReader($response.GetResponseStream()) $sr.BaseStream.Position = 0 $sr.DiscardBufferedData() $body = $sr.ReadToEnd() Write-Debug "Error Body: $body" # try parsing the body try { $acmeError = $body | ConvertFrom-Json } catch { Write-Warning "Response body was not JSON parseable" # re-throw the original exception throw $ex } # check for badNonce and retry once if (!$NoRetry -and $freshNonce -and $acmeError.type -and $acmeError.type -like '*:badNonce') { $Header.nonce = $script:Dir.nonce Write-Debug "Retrying with updated nonce" return (Invoke-ACME $Uri $Key $Header $PayloadJson -NoRetry) } # throw the converted AcmeException throw [AcmeException]::new($acmeError.detail,$acmeError) } } |