letsencrypt::Client method getCertificate (public)

 <instance of letsencrypt::Client[i]> getCertificate

Defined in /usr/local/ns/tcl/letsencrypt/letsencrypt-procs.tcl

This method does all the steps required to obtain a certificate, such as - selecting the API (production or staging), - registering a new account if necessary, - create public and private key for the account, - issuing a certificate request, - obtaining the certificate, and - installing the certificate. If called interactivaly, the progress is logged to the console, otherwise just into the system log.

Partial Call Graph (max 5 caller/called nodes):
%3

Testcases:
No testcase defined.
Source code:
ns_log notice "letsencrypt client: domains <${:domains}> background ${:background}"

if {${:domains} eq ""} {
    #
    # Are values for the domains specified in the
    # NaviServer configuration file?
    #
    set :domains [ns_config ns/server/[ns_info server]/module/letsencrypt domains]
    #ns_log notice "letsencrypt client: domains from NaviServer configuration file: <${:domains}>"

}

if {${:domains} eq "" && [ns_conn isconnected]} {
    #
    # Still no values. Try to get it from the query parameters
    #
    #ns_log notice "letsencrypt client: we need a queryget"
    set :domains [ns_queryget domains ""]
    ns_log notice "letsencrypt client: domains from query <${:domains}> background ${:background}"
    #
    # If the domain names were already submitted in the form
    # (or via query parameters), we have all data we
    # need.
}

ns_log notice "letsencrypt client: domains <${:domains}> background ${:background}"
if {${:domains} eq ""} {
    #
    # If we have still no values, provide the user with a
    # form to fill-in the data and to continue from there.
    # But this works only, when we are not called in the
    # background.
    #
    if {${:background}} {
        error "letsencrypt: either provide '-domains ...' or run from a connection thread"
    }
    ns_log notice "letsencrypt: have to return domainForm"
    :domainForm
    return
}

set :domain    [lindex ${:domains} 0]
set :sans      [lrange ${:domains} 1 end]

set config {
    staging    {https://acme-staging-v02.api.letsencrypt.org/directory}
    production {https://acme-v02.api.letsencrypt.org/directory}
}

#
# Make sure, the sslpath exists
#
file mkdir ${:sslpath}
set :accoutKeyFile ${:sslpath}/letsencrypt-${:API}-account.key
ns_log notice "letsencrypt client: call start of report"

#
# Start output
#
:startOfReport

ns_log notice "letsencrypt: getAPIurls"

#
# Always get first the API URLs
#
:getAPIurls $config

#
# Create or reuse an account
#
if {[file exists ${:accoutKeyFile}]} {
    #
    # We have already registered in the past successfully at
    # Let's Encrypt and signed the agreement.
    #
    :log "Reuse existing account registration at Let's Encrypt (${:accoutKeyFile})<br>"

    :parseAccountKey
    :getNonce

    set payload [subst {{"termsOfServiceAgreed": true, "onlyReturnExisting": true, "contact": \["mailto:webmaster@${:domain}"\]}}]
    set status [:send_signed_request [:URL newAccount] $payload]

    if {$status eq "400"} {
        :abortMsg $status "authorization for existing account failed"
        return
    } else {
        set :kid [ns_set iget ${:replyHeaders} "location"]
        :log "<pre>registration headers contained kid ${:kid}\n</pre>"
    }

} else {

    set status [:registerNewAccount]
    if {$status >= 400} {
        :abortMsg $status "Registration"
        return
    }

    set status [:signAgreement]
    if {$status >= 400} {
        :abortMsg $status "Agreement"
        return
    }
}

#
# Create a new order for the domains
#
file delete -force [ns_server pagedir]/.well-known
file mkdir [ns_server pagedir]/.well-known

:log "Creating new order...<br>"
set ids {}
foreach domain ${:domains} {
    lappend ids [subst {{"type""dns""value""$domain"}}]
}
set payload [subst {{"identifiers": \[[join $ids ,]\]}}]
:log "... payload: <pre>$payload</pre>"

set httpStatus [:send_signed_request [:URL newOrder] $payload]
if {$httpStatus >= 400} {
    :abortMsg $httpStatus "Order failed"
    return
}
set orderDict [json::json2dict ${:replyText}]
set authorizations [dict get $orderDict authorizations]
set identifiers [dict get $orderDict identifiers]
set orderFinalizeURL [dict get $orderDict finalize]

#:log "<pre>authorizations:\n$authorizations\norderFinalizeURL:$orderFinalizeURL</pre>"

if {[llength $authorizations] != [llength ${:domains}]} {
    :abortMsg $httpStatus "number of domains ([llength ${:domains}]) differs from number of authorizations ([llength $authorizations])"
    return
}

foreach domain ${:domains} auth_url $authorizations id $identifiers {
    set status [:authorizeDomain $auth_url [dict get $id value]]
    if {$status in {invalid}} {
        :log [ns_trim -delimiter | [subst {
            |Validation of domain $domain failed (final status $status).
            |<p>Please issue a corrected certificate check.
        }]]
        return
    }
}

file delete -force [ns_server pagedir]/.well-known

#
# Get certificate
#
set status [:certificateRequest $orderFinalizeURL]
if {$status >= 400} {
    :abortMsg $status "Certificate request"
    return
}

#
# Install certificate and update configuration file
#
:certificateInstall
:updateConfiguration

if {${:API} eq "production"} {
    #
    # Everything was updated, We can trigger the reload
    # operation by sending SIGHUP to the nsd process
    #
    ns_kill [pid] 1
    :log [ns_trim -delimiter | [subst {
        |<br>The new certificate is installed and was
        |reloaded via SIGHUP. For old versions of
        |NaviServer, restart your NaviServer instance and
        |check results on
        |<a href="https://${:domain}">https://${:domain}</a>.
        |<p> }]]

    :log "<p>Certificate were reloaded by sending SIGHUP to nsd"
} else {
    :log "<p><strong>Warning:</strong> no automated reloading"  "when using the 'staging' environment<p>"
}
XQL Not present:
Generic, PostgreSQL, Oracle
[ hide source ] | [ make this the default ]
Show another procedure: