Copyright © 2020-2024 Mark G. Daniel
This program, comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it under the
conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version.
http://www.gnu.org/licenses/gpl.txt
wµCME (pronounced "wack-mee") is a Certificate Management Environment for the Let's Encrypt ™ service.
"The objective of Let's Encrypt and the ACME protocol is to make it possible to set up an HTTPS server and have it automatically obtain a browser-trusted certificate, without any human intervention [and at no cost]. This is accomplished by running a certificate management agent on the web server."https://www.letsencrypt.org
https://en.wikipedia.org/wiki/Let's_EncryptLet's Encrypt is a trademark of the Internet Security Research Group. All rights reserved.
Use of Let's Encrypt requires compliance with the terms and conditions described in the Subscriber Agreement document which can be found at the top the following URL.
wµCME is based on a port and adaptation of uacme ACME v2 implementation by Nicola Di Lieto, licensed and distributed under GPLv3.
https://github.com/ndilieto/uacme
https://github.com/ndilieto/uacme/blob/master/COPYING
Automatic Certificate Management Environment (ACME) describes a protocol that a certification authority (CA) and an applicant can use to automate the process of verification and certificate issuance. The protocol also provides facilities for other certificate management functions, such as revocation. At the time of writing it is a Proposed Standard and working document of the Internet Engineering Task Force (IETF).
https://ietf-wg-acme.github.io/acme/
The uacme application is a lightweight implementation of the ACME v2 protocol, designed to interact with the Let's Encrypt service for the automated issuance, renewal and revocation of server certificates.
wµCME uses uacme to provide the core of the required functionality, with code modifications for use on [Open]VMS, as well as additional code specifically for WASD web server TLS service certificate management. See Implementation.
wµCME is a contraction of WASD µ Certificate Management Environment. The "u" in uacme is not explained but is assumed to be a mu as used for micro, meaning small. Everywhere other than documentation the name reads "wucme".
wµCME is tailored for WASD.
Apache has mod_md for ACME.
How it Operates
wµCME has three roles
When acting as certificate management agent wµCME executes within the process named "wuCME-active". This process is created and managed by the server. wµCME uses the DLM to ensure only a single process manages certificates with multi-instance nodes and clusters (except if the logical name WUCME_ACTIVE is defined – see below). In such environments there may be "wuCME-standby" processes waiting for circumstances requiring another to become active. Largely quiescent, once a day (at twenty minutes past midnight) wµCME checks certificate expiry and attempts to renew if required. Let's Encrypt issues certificates with a ninety day lifetime and wµCME begins attempting renewal the recommended thirty days advance of expiry.
Authority to request a certificate is established by the requesting agent demonstrating control over the corresponding domain name (service). wµCME employs the ACME http-01 challenge, where a unique and cryptographically exchanged resource is accesssed by Let's Encrypt using a well-known URI on the standard cleartext service (port 80). wµCME will deploy an ad hoc port 80 service if one is not available. Successful access (to each of any multiple domain names) satisfies Let's Encrypt and certificate issue proceeds. To perform this function wµCME is activated as a script.
wµCME generates and maintains two private cryptographic keys. One to identify a unique account with Let's Encrypt and sign HTTP transactions. Another to sign the Certificate Signing Request (CSR) when requesting a certificate, and subsequently to authorise use of that generated certificate by the server (i.e. the traditional private key). wµCME supports one account, potentially managing multiple certificates, per system.
Both successful and failed renewal attempts optionally produce an OPCOM
message and/or email to configurable targets. Successful renewal executes an
action to (re)load the renewed certificate(s) into the server.
Usage
Certificates remain in the usual WASD_ROOT:[LOCAL] location and wµCME-generated certificates are placed directly into this directory. To locate the certificates elsewhere define the WUCME_HERE logical name. For example
$ DEFINE /SYSTEM /EXECUTIVE WUCME_HERE ELSE:[WHERE]
If required the definition should be added to the startup procedure.
All wµCME certificate files are prefixed with with WUCME_C_ and key files with WUCME_K_. Domain-specific files are followed by the host name, or first of multiple host names (see below), of the certificate request, made ODS-2 compliant. Using www.example.org for an example:
# WASD_CONFIG_GLOBAL [SSLcert] WASD_ROOT:[LOCAL]WUCME_C_WWW_EXAMPLE_ORG.PEM
# WASD_CONFIG_SERVICE [[https://www.example.org]] [ServiceSSLcert] WASD_ROOT:[LOCAL]WUCME_C_WWW_EXAMPLE_ORG.PEM
wµCME always appends the private key to the certificate.
After wµCME renews a certificate it is automatically loaded into the server(s).
wµCME does not support wildcard certificates but will issue a certificate for multiple hosts (at time of writing, up to one hundred) by using altnames. This means that a single certificate may be used and managed for multiple, or even all, services of a site. However, each one will be individually challenged (see 2. The ACME http-01 challenge...) and must be accessible from the Internet. And of course wµCME also supports multiple certificates allowing for a very large pool of Let's Encrypt -secured sites per server.
Command-line usage is via a foreign verb or MCR.
$ wucme == "$cgi_exe:wucme" $ mcr cgi_exe:wucme
wµCME requires account SYSPRV for
command-line use.
Account Registration
An account must be registered with Let's Encrypt before wµCME can be used for certification. A private cryptographic key is created to identify the end-user to the CA. This is performed manually at the command-line. Optionally, an email address may be supplied.
$ wucme register webmaster@example.com
Generally only required the once, it can be done again at any time to
(re)create an(other unique) account. If this happens an initial
certification must also be redone for all managed services.
Initial Certification
Before wµCME can be used for maintenance an initial certification must be made. This is performed manually at the command-line. It is also necessary when the number or composition of services changes. Failure to maintain the currency of a certificate will likely result in failure at next automated renewal.
First, be sure to test challenge processing for all services to be certified, as described in 2. The ACME http-01 challenge....
Multiple host names can be space or comma-separated.
$ wucme issue www.example.org
Once a certificate or certificates are successfully generated they need to be loaded into the running server. For WASD v11.1.0 or later this may be performed using
$ httpd /do=ssl=cert=loador for earlier releases
$ httpd /do=restart[=now]
When in detached operation wµCME automatically will perform the certificate load function.
The initial certification step is used for each certificate
to be requested.
This can be done at time of initial installation and any time following
that to provision additional services.
Remember that each certificate may support up to one hundred
sites.
With a non-trivial number of sites to be certified it is suggested that a command procedure be created in WASD_ROOT:[LOCAL] that contains the necessary command. The can then be edited and executed as required. For example:
$! CERTIFY_EXAMPLE_SERVICES.COM $ wucme = "$cgi_exe:wucme" $ wucme issue www.example.org,two.example.org,three.example.org,- four.example.org,five.example.org,six.example.org,seven.example.org,- eight.example.org,nine.example.org,ten.example.org
Note that Let's Encrypt has limits on the number of certifications in a given period.
https://letsencrypt.org/docs/rate-limits/
Functions available from the VMS command-line.
Command-Line | Description | ||
---|---|---|---|
certificates | List the certificates currently being managed. | ||
check /ca | Test that the CA is accessible by performing a directory query with it. | ||
check /http01 | Activate the internal challenge responder binding to port 80. | ||
check /log | Display the content of the most recent "wuCME-active" process log. | ||
check /mail | Test the WUCME_MAIL logical name reporting target. | ||
check /opcom | Test the WUCME_OPCOM logical name reporting target. | ||
issue host(s) | Request Let's Encrypt to issue a certificate that includes the specified host or comma-separated list of hosts. | ||
manage | Manually run the certificate management function normally executed once a day by the "wuCME-active" process. | ||
revoke file | Request Let's Encrypt to revoke the certificate contained in the specified file. | ||
version | Display the executable version. |
The underlying uacme CLI and functionality is available by prefixing the command-line parameters with a --uacme option.
The UACME(1) Manual Page is available at
https://ndilieto.github.io/uacme/
and locally as a
PDF.
Installation
Basic steps:
Expanded descriptions:
https://wasd.vsm.com.au/wasd/
$ SET DEFAULT WASD_ROOT:[SRC] $ UNZIP <location>:WUCMEnnn.ZIP $ UNZIP <location>:WUCMEnnn-<platform>.ZIP $ SET DEFAULT [SRC.WUCME]
$ @BUILD_WUCME <p1> <p2>
Parameter | Purpose | Description | ||
---|---|---|---|---|
P1 | build mode | BUILD (compile then link), COMPILE or LINK | ||
P2 | SSL package root |
Directory specification of SSL package. Examples:
SSL111$ROOT:[000000] SSL3$ROOT:[000000] WASD_ROOT:[SRC.OPENSSL-1_1_1E] SYS$COMMON:[OPENSSL]Requires OpenSSL 1.1.1 or later |
For example:$ @BUILD_WUCME BUILD SSL3$ROOT:[000000]
$ COPY [.OBJ_<platform>]WUCME.EXE CGI_EXE:
Then copy the wrapper procedure for the ALPN-TLS-01 support.
$ COPY ACME_TLS_1.COM CGI_BIN:
This procedure also will accept DCL symbols representing the runtime logical names and these may be used to customise the site startup. For example:
$ WUCME_MAIL = "SYSTEM,mark.daniel@wasd.vsm.com.au" $ WUCME_OPCOM = "CENTRAL,NETWORK" $ @WASD_ROOT:[SRC.WUCME]WUCME_STARTUP.COM
Execute the wµCME startup procedure. For example:
$ @WASD_ROOT:[SRC.WUCME]WUCME_STARTUP.COM
Ensure the startup procedure and parameters are edited into the system startup.
When successfully certified restart the server to use the fresh Let's Encrypt certificate(s).
Sites have all manner of local requirements and mapping rules, often
associated with multiple independent hosting services. With non-trivial sites
there is no single recipe to support all aspects of
wµCME functionality. Essential is the
Let's Encrypt challenge mapping and the proctoring of the
All logical names must be defined in the SYSTEM table.**
Basic steps:
**WUCME_STARTUP.COM can assist with this.
Expanded descriptions:
# WASD_CONFIG_GLOBAL [DclScriptProctor] 1 /cgi-bin/wucme /cgi-bin/wucme
A mapping rule is required to activate this as a script. This can be a
simple, global rule allowing the activation of scripts located in
# WASD_CONFIG_MAP exec /cgi-bin/* /cgi-bin/*
If this is not implemented on a site, a constrained rule can be provided allowing activation of only proctored scripts and the wµCME executable in particular.
# WASD_CONFIG_MAP if (request-method:) exec /cgi-bin/* /cgi-bin/* endif
This process appears in the system as "wuCME-active". The server needs to be restarted at initial configuration and thereafter automatically maintains an instance of this process. Also see Catch-all Config for a combined challenge and script mapping for all services.
# WASD_CONFIG_SERVICE [[https://www.example.org]] [ServiceSSLcert] WASD_ROOT:[LOCAL]WUCME_C_WWW_EXAMPLE_ORG.PEM
# WASD_CONFIG_GLOBAL [SSLcert] WASD_ROOT:[LOCAL]WUCME_C_WWW_EXAMPLE_ORG.PEM
$ DEFINE /SYSTEM /EXECUTIVE WUCME_MAIL "system,smtp%""whomever@wherever""" $ DEFINE /SYSTEM /EXECUTIVE WUCME_OPCOM "central,software,oper2"
Where multiple installations exist, each with their own WASD_ROOT:[LOCAL] certificate repository, then one system per repository must have the logical name WUCME_ACTIVE defined.
$ DEFINE /SYSTEM /EXECUTIVE WUCME_ACTIVE *
Where all clustered systems are independent a cluster-wide logical could be used.
$ DEFINE /CLUSTER /EXECUTIVE WUCME_ACTIVE *
URI | Description | ||
---|---|---|---|
/cgi-bin/wucme/admin/cert | Reports on the current certificate(s) being managed. | ||
/cgi-bin/wucme/admin/log | Reports the current "wuCME-active" log. | ||
/cgi-bin/wucme/admin/check/ca | Test that the CA is accessible by performing a directory. | ||
/cgi-bin/wucme/admin/check/challenge |
Test the challenge responder, either local mapped port 80 or internal ad hoc
responder.
Does not check alpn-tls-01 (see below). | ||
/cgi-bin/wucme/admin/check/load | Test certificate (re)load. | ||
/cgi-bin/wucme/admin/check/mail | Test WUCME_MAIL logical name by sending a message. | ||
/cgi-bin/wucme/admin/check/opcom | Test WUCME_OPCOM logical name by sending a message. |
The admin path (only) must be subject to authorisation. For example:
# WASD_CONFIG_AUTH ["VMS credentials"=WASD_VMS_RW=id] /httpd/-/admin/* r+w,https: /cgi-bin/wucme/admin/* read,https:
All services to be certified, or when automatically recertified, must a receive a challenge for LE to demonstrate your control of the server.
The two methods available to wµCME for this are http-01 and alpn-tls-01
https://letsencrypt.org/docs/challenge-types/
Must be provided by port 443 service(s) on the server system.
No additional rule(s) required.
Checking alpn-tls-01 code pathways is less intuitive but can be performed in a straight-forward manner, using the OpenSSL s_client utility on VMS or other platform:
openssl s_client -connect <host>:443 -servername <host> -alpn "acme-tls/1"
Use WATCH [x]SSL when the ACME agent being activated, and as expected failing, with messages similar to the following:
… SESOLACM 0290 000003 SSL ACME acme-tls/1 AGENT /cgi-bin/acme_tls_1| … SESOLANE 0403 000002 SSL BEGIN retry| … SESOLACM 0340 ****** SSL ACME 500 base642bin() the.host.name 1 0| … SESOLACM 0396 000003 SSL ACME acme-tls/1 %X0000002C (%SYSTEM-F-ABORT, abort)|
This demonstrates the WASD ACME code and agent code is operating correctly.
The failure and abort being the result of having no real LE transaction
involved.
Adding [x]CGI and [x]DCL provides further detail.
This requires URI mapping to allow wµCME to process the challenge and response. This rule must be applied for any service to be challenged by Let's Encrypt. For example:
# WASD_CONFIG_MAP map /.well-known/acme-challenge/* \ /cgi-bin/wucme/.well-known/acme-challenge/* map=once
While this can be confined to port 80 services there is no significant reason why it cannot be mapped for all (early in the mapping configuration file). The /.well-known/acme-challenge/ cannot be subject to authorisation. Also see Catch-all Config for a combined challenge and script mapping for all services.
Challenge checking may directly be performed using a browser or command-line utility such as cURL and the URI /.well-known/acme-challenge/ The URI should return "Challenge received but no such token." Any other response must be investigated.
$ curl http://the.host.name/.well-known/acme-challenge/ Challenge received but no such token.
Employ the ad hoc port 80 (for checking) from the command-line using
$ wucme check /http01
and then the challenge checking as described above.
From the wµCME beachhead…Triage at the bleeding edge. Individual sites can be non-trivial. Not all resources, including general scripting access, may be present on all services. The following rules, placed at the very beginning of the configuration file, will provide all wµCME-required resources to all services of an installation, in a manner constrained to wµCME usage.
# WASD_CONFIG_MAP [[*]] # ACME challenge processing redirect /.well-known/acme-challenge/* \ /cgi-bin/wucme/.well-known/acme-challenge/* # wuCME proctor and script activation if (request-method: || request-uri:/cgi-bin/wucme/*) exec /cgi-bin/* /cgi-bin/* map=once endif
If blanket authorisation is applied to a service requiring challenge processing, the following configuration may be used to exempt the challenge.
# WASD_CONFIG_AUTH [[*]] # ACME challenge processing [NONE] **/.well-known/acme-challenge/* read
The port required an adaptation of C-language code to operate with VMS, DECC compiler and the post-VMS-v7.0 C-RTL, as well as some Unix assumptions regarding the file-system and like. The wµCME executable provides file-system access to otherwise protected resources using INSTALLed SYSPRV. Conditional compilation has general VMS adaptations using #if[n]def __VMS while wµCME-specific code uses #if[n]def IS_WUCME.
As WASD is designed to allow and known to be installed on ODS-2 volumes, wµCME file-system elements are explicitly constrained to ODS-2 syntax, in particular, non- file type periods are substituted with underscores, making the host name an.example.net into an_example_net in the file-system.
While uacme uses the cURL library to perform its
HTTP transactions, the issues with having cURL as a dependency on VMS systems
has necessitated wµCME implementing its own
HTTP client.
Problems?
[{ "type": "urn:acme:error:unauthorized", "detail": "Invalid response from http://host.name/.well-known/acme-challenge/ahZfPYUH1TbtjGC4A6B7SAS--MYez54oYg6ShEJFHVE: 8< snip 8<" "status": 403 }]
In this case the challenge cannot be accessed due to lack of permission of some description. See test challenge processing.
[{ "type": "urn:acme:error:unauthorized", "detail": "Error creating newcert :: authorizations for these names not found or expired: the.host.name", "status": 403 }]
Either the account registration step has been neglected, or Let's Encrypt has expired the account created when originally performed. Undertake the account registration and initial certification steps (again).
{ "type": "urn:ietf:params:acme:error:malformed", "detail": "NewOrder request included invalid non-DNS type identifier: type \"ip\", value \"a host name\"", "status": 400 }
The TCP/IP Services programming documentation describes getaddrinfo() (used in standard module CRYPTO.C) as:
This function takes a service location (nodename) or a service name (servname), or both, and returns a pointer to a linked list of one or more structures of type addrinfo. Its members specify data obtained from the local hosts database TCPIP$ETC:IPNODES.DAT file, the local TCPIP$HOSTS.DAT file, or one of the files distributed by DNS/BIND.Remove any local references of your services from local DNS otherwise renewal will fail.
$ tcpip set host server.local /address=server IP address /noalias=host name (on Internet)
$ DEFINE /SYSTEM /EXECUTIVE WUCME_VERBOSE *Restart is not required.
A few URLs of interest and example.
Many thanks to Nicola Di Lieto for making uacme freely available.