# Helps to build a CAC-enabled redbean web server (see https://redbean.dev/ and # https://github.com/jart/cosmopolitan) # # Requirements: # Make (probably GNU make, we use GNU make extensions, but others may work) # Curl command line client # zip and unzip command line clients (InfoZIP is the norm) # OpenSSL command line interface (for cert handling and conversion) # coreutils (for sha256sum) VENDORED_REDBEAN := redbean-2.0.3.com VENDORED_REDBEAN_SHA256_SUM := 41ce74d2be25d77a907bf90e6c15115feb154e5dea2de26284055fe2faa4175a DOD_ROOT_PKI_VERSION := 5.9 # Latest releases seem to have CA 3 through CA 5 only. DOD_ROOT_CERTS := $(addprefix usr/share/ssl/root/, dod-root-3.pem dod-root-4.pem dod-root-5.pem) .PHONY: all clean distclean # Ensure these files are cleaned up by make as well .INTERMEDIATE: $(VENDORED_REDBEAN) certificates_pkcs7_DoD.zip .init.lua # Uncomment this next line if you don't want intermediate files removed #.SECONDARY: # Default target all: redbean-cac.exe # This part is to allow for multi-line variables to be output to the shell # without confusing either Make or the shell. First we need a way to define a # Make variable that holds just a newline so we can use it in a Make subst # command later to convert newline to \n, then have the shell turn it back into # a real newline later using printf. # Two lines on purpose, Make removes that last newline define newline endef # This all needs to be escaped for syntax special to Make btw, including $. # Note that this will then all be dumped into the shell command line in a big # giant ' ' (single quote) wrapper for a printf format string. This means: # No ' characters anywhere, not even in comments # % must be escaped for printf # $ must be escaped for Make define init_Lua_script -- Lua code to force enable TLS and TLS client verification. Note that there -- are other root certs in the default redbean root store so if we want to be -- 100%% sure we would have to remove those too. But it is possible to apply -- that extra validation within OnHttpRequest as well. ProgramSslClientVerify("true") ProgramSslRequired("true") -- Redbean runs this for each HTTP request, so we insert handler code here to -- pull out the peer certificate information. function OnHttpRequest() -- Undocumented function but appears to return the X.509 Subject Name from -- the peer cert, when using SSL peer_cert = GetSslIdentity() if peer_cert then Log(kLogVerbose, "The peer subject name is "..peer_cert) _, _, peer_name = string.find(peer_cert, "CN=([A-Z0-9.]+)$$") if peer_name then path = GetPath() Log(kLogInfo, "Request to "..path.." from "..peer_name) else Log(kLogWarn, "Unable to decode this to a peer name!") end end Route() -- return to Redbean default processing end endef # Magic alert... the openssl command dumps the DER-encoded certificates in each batch into multiple text-based PEM certs. # But we only need the *root* cert. The sed command is used to skip lines (using -n to suppress printout) until we find # the right certificate is coming up (noted by a line with subject= ... CN = DoD Root CA). # When we find this line, sed will then start executing the stuff in the { } block until we reach the end of the range # (the -----END CERTIFICATE part). # If the block were just p (for print) we'd end up with a file looking like # subject= blah blah blah # # issuer= blah blah blah # # -----BEGIN CERTIFICATE----- # .... # -----END CERTIFICATE----- # # but this won't work. So the sed commands in the block just look for lines above the ----BEGIN (ie. subject, issuer or blank lines) # and delete them, and prints the rest. usr/share/ssl/root/dod-root-%.pem: Certificates_PKCS7_v$(DOD_ROOT_PKI_VERSION)_DoD/Certificates_PKCS7_v$(DOD_ROOT_PKI_VERSION)_DoD_DoD_Root_CA_%.der.p7b usr/share/ssl/root @echo "[ CONV ] Extracting individual root cert $* from bundle and encoding to PEM format" @openssl pkcs7 -inform DER \ -in $< -print_certs \ | sed -n '/^subject=.*CN = DoD Root CA/,/^-----END CERTIFICATE/{ /^subject=/d; /^issuer=/d; /^$$/d; p}' \ > $@ # Create directory if non-existent usr/share/ssl/root: @echo "[ DIR ] Creating SSL root directory to load into Redbean web server" @mkdir -p $@ Certificates_PKCS7_v$(DOD_ROOT_PKI_VERSION)_DoD/Certificates_PKCS7_v$(DOD_ROOT_PKI_VERSION)_DoD_DoD_Root_CA_%.der.p7b: certificates_pkcs7_DoD.zip @echo "[ CERT ] Extracting DoD Root CA $* bundle from certificate bundle" @unzip -n $< $@ >/dev/null certificates_pkcs7_DoD.zip: @echo "[ DOWNL] Downloading DoD root certificates from public website" @curl -s -o "$@" "https://dl.dod.cyber.mil/wp-content/uploads/pki-pke/zip/$@" $(VENDORED_REDBEAN): @echo "[ DOWNL] Downloading $@" @curl -s -o "$@" "https://redbean.dev/$@" @echo "[ CKSUM] Ensuring executable is not changed from expected" @test $$(sha256sum $@ | awk '{ print $$1 }') = $(VENDORED_REDBEAN_SHA256_SUM) \ && echo "[ OK ] The checksum for $@ is correct" \ || { echo "[!!!!!!] The checksum for $@ is incorrect!" ; exit 1; } .init.lua: $(VENDORED_REDBEAN) @echo "[ INIT ] Setting up Redbean web server init script to force CAC usage" @unzip -n $< $@ >/dev/null @printf '$(subst $(newline),\n,${init_Lua_script})' >> $@ redbean-cac.exe: $(VENDORED_REDBEAN) $(DOD_ROOT_CERTS) .init.lua @echo "[ ZIP ] Infusing DoD root certs into Redbean web server" @mv $(VENDORED_REDBEAN) $@ @zip -r $@ $(DOD_ROOT_CERTS) >/dev/null @echo "[ INIT ] Updating Redbean web server init script to activate CAC" @zip $@ .init.lua >/dev/null @echo "[ CHMOD] Making script executable" @chmod +x $@ @echo "" @echo Redbean web server "$@" initialized with DoD root certificates @echo "" @echo "To try it out, run ./$@ -p 443 (you can choose a different port as well, like 8080 for non-root)" @echo "Then go to https://localhost:443/ in a browser on the same machine. Replace 443 with your chosen port" @echo "To serve files from a specific directory add '-D /path/to/dir' to the command line." @echo "" # Leave room for make to mention that it's cleaning intermediate files # Intermediate files should be removed by make automatically (see .INTERMEDIATE above) so we # don't try to handle those here. clean: @echo "[ RM ] Removing stray directories" @rm -r usr @rmdir Certificates_PKCS7_v$(DOD_ROOT_PKI_VERSION)_DoD # should now be empty