diff options
-rw-r--r-- | openssl-controller.lua | 43 | ||||
-rw-r--r-- | openssl-generatecacert-html.lsp | 15 | ||||
-rw-r--r-- | openssl-html.lsp | 12 | ||||
-rw-r--r-- | openssl-model.lua | 223 | ||||
-rw-r--r-- | openssl-putcacert-html.lsp | 2 | ||||
-rw-r--r-- | openssl-status-html.lsp | 18 | ||||
-rw-r--r-- | openssl-viewcert-html.lsp | 2 | ||||
-rw-r--r-- | openssl-viewrequest-html.lsp | 2 | ||||
-rw-r--r-- | openssl.roles | 2 |
9 files changed, 253 insertions, 66 deletions
diff --git a/openssl-controller.lua b/openssl-controller.lua index af92ec5..ff9fb91 100644 --- a/openssl-controller.lua +++ b/openssl-controller.lua @@ -5,19 +5,21 @@ require("getopts") default_action = "status" +local sslstatus + mvc={} mvc.pre_exec = function(self) - if self.conf.action ~= "status" and self.conf.action ~= "editconfigfile" then - local verify = self.model.verifyopenssl() - if verify.value == false then - redirect(self) - end + sslstatus = self.model.getstatus() + if (sslstatus.value.version.errtxt and self.conf.action ~= "status") + or (sslstatus.value.conffile.errtxt and self.conf.action ~= "status" and self.conf.action ~= "editconfigfile") + or ((sslstatus.value.cacert.errtxt or sslstatus.value.cakey.errtxt) and self.conf.action ~= "status" and self.conf.action ~= "editconfigfile" and self.conf.action ~= "putcacert" and self.conf.action ~= "generatecacert") then + redirect(self) end end -- Show openssl status status = function(self) - return self.model.getstatus() + return sslstatus end -- View all pending and approved requests and revoked certificates @@ -143,15 +145,40 @@ getrevoked = function(self) end -- Put the CA cert --- FIXME this won't work because haserl doesn't support file upload. Untested putcacert = function(self) local retval = self.model.putca(self.clientdata.ca, self.clientdata.password, self.clientdata.Upload) + if self.clientdata.Upload then + if not retval.errtxt then + redirect(self) + end + end retval.type = "form" - retval.option = "Upload" retval.label = "Upload CA Certificate" + retval.option = "Upload" + return retval end +-- Generate a self-signed CA +generatecacert = function(self) + local request + if self.clientdata.Generate then + -- Try to submit the request + request = self.model.generateca(self.clientdata) + if not request.errtxt then + redirect(self) + end + else + request = self.model.getnewcarequest() + end + + request.type = "form" + request.label = "Generate CA Certificate" + request.option = "Generate" + + return request +end + editconfigfile = function(self) local saved = false if self.clientdata.Save then diff --git a/openssl-generatecacert-html.lsp b/openssl-generatecacert-html.lsp new file mode 100644 index 0000000..465f53f --- /dev/null +++ b/openssl-generatecacert-html.lsp @@ -0,0 +1,15 @@ +<? local form, viewlibrary = ... ?> +<? require("viewfunctions") ?> + +<? --[[ DEBUG INFORMATION +io.write(html.cfe_unpack(form)) +--]] ?> + +<H1><?= form.label ?></H1> +<? + form.action = "generatecacert" + local order = { "countryName", "stateOrProvinceName", "localityName", "organizationName", + "organizationalUnitName", "commonName", "emailAddress" } + displayform(form, order) +?> + diff --git a/openssl-html.lsp b/openssl-html.lsp index d8244b9..adac74e 100644 --- a/openssl-html.lsp +++ b/openssl-html.lsp @@ -104,12 +104,24 @@ end ?> <? else ?> <TABLE> <TR style="background:#eee;font-weight:bold;"> + <TD style="padding-right:20px;white-space:nowrap;" class="header">Action</TD> <TD style="padding-right:20px;white-space:nowrap;" class="header">User</TD> <TD style="padding-right:20px;white-space:nowrap;" class="header">Cert Type</TD> <TD style="padding-right:20px;white-space:nowrap;" class="header">Common Name</TD> <TD style="white-space:nowrap;" class="header">Serial Num</TD> <? for i,cert in ipairs(revoked) do ?> <TR> + <TD> + <? if session.permissions.openssl.viewcert then ?> + <?= html.link{value="viewcert?cert="..cert.name, label="View "} ?> + <? end ?> + <? if session.permissions.openssl.getcert then ?> + <?= html.link{value="getcert?cert="..cert.name, label="Download "} ?> + <? end ?> + <? if session.permissions.openssl.deletecert then ?> + <?= html.link{value="deletecert?cert="..cert.name, label="Delete "} ?> + <? end ?> + </TD> <TD><?= cert.user ?></TD> <TD><?= cert.certtype ?></TD> <TD><?= cert.commonName ?></TD> diff --git a/openssl-model.lua b/openssl-model.lua index 29a9b5f..5f4897d 100644 --- a/openssl-model.lua +++ b/openssl-model.lua @@ -1,6 +1,7 @@ module(..., package.seeall) require("html") +require("validator") -- There are two options of how to allow users to specify the type of certificate they want - the request extensions -- and the ca signing extensions. We have opted for making all requests look the same (same extensions) and defining @@ -31,6 +32,23 @@ local extensions = { "basicConstraints", "nsCertType", "nsComment", "keyUsage", -- list of entries that must be found in ca section local ca_mandatory_entries = { "new_certs_dir", "certificate", "private_key", "default_md", "database", "policy" } +-- Create a cfe with the distinguished name defaults +local getdefaults = function() + local defaults = cfe({ type="group", value={} }) + config = config or getopts.getoptsfromfile(configfile) + local distinguished_name = config.req.distinguished_name or "" + + -- Get the distinguished name defaults + for i, name in ipairs(distinguished_names) do + defaults.value[name.name] = cfe({ label=name.label, + value=config[distinguished_name][name.name .. "_default"] + or config[distinguished_name]["0."..name.name.."_default"] or "", + descr=config[distinguished_name][name.name] or config[distinguished_name]["0."..name.name] }) + end + + return defaults +end + -- Validate the values of distinguished names using the min/max found in the config file local validate_distinguished_names = function(values) config = config or getopts.getoptsfromfile(configfile) @@ -114,16 +132,18 @@ local handle_req_clientdata = function(clientdata, defaults) local success success, defaults = validate_distinguished_names(defaults) - local foundcert=false - for i,cert in ipairs(defaults.value.certtype.option) do - if defaults.value.certtype.value == cert then - foundcert=true - break + if defaults.value.certtype then + local foundcert=false + for i,cert in ipairs(defaults.value.certtype.option) do + if defaults.value.certtype.value == cert then + foundcert=true + break + end + end + if not foundcert then + success = false + defaults.value.certtype.errtxt = "Invalid certificate type" end - end - if not foundcert then - success = false - defaults.value.certtype.errtxt = "Invalid certificate type" end return success, defaults @@ -139,44 +159,41 @@ local getconfigpath = function(section, value) return result end --- FIXME we need to make sure necessary files / directories / private key are there -verifyopenssl = function() - -- set the working directory once for model - posix.chdir(openssldir) - - local retval = false - if fs.is_file(configfile) then - config = config or getopts.getoptsfromfile(configfile) - if config and config.ca and config.ca.default_ca then - local cacert_file = getconfigpath(config.ca.default_ca, "private_key") - if fs.is_file(cacert_file) then - retval=true - end - end - end - return cfe({ type="boolean", value=retval, label="openssl verified" }) +local copyca = function(cacert, cakey) + config = config or getopts.getoptsfromfile(configfile) + local certpath = getconfigpath(config.ca.default_ca, "certificate") + local cmd = "cp "..cacert.." "..certpath + local f = io.popen(cmd) + f:close() + local keypath = getconfigpath(config.ca.default_ca, "private_key") + local cmd = "cp "..cakey.." "..keypath + local f = io.popen(cmd) + f:close() end getstatus = function() require("processinfo") + -- set the working directory once for model posix.chdir(openssldir) local value,errtxt=processinfo.package_version(packagename) local version = cfe({ value=value, errtxt=errtxt, label="Program version" }) local conffile = cfe({ value=configfile, label="Configuration file" }) local cacert = cfe({ label="CA Certificate" }) local cacertcontents = cfe({ type="longtext", label="CA Certificate contents" }) + local cakey = cfe({ label="CA Key" }) if not fs.is_file(configfile) then conffile.errtxt="File not found" cacert.errtxt="File not defined" cacertcontents.errtxt="" + cakey.errtxt="File not defined" else config = config or getopts.getoptsfromfile(configfile) if (not config) or (not config.ca) or (not config.ca.default_ca) then conffile.errtxt="Invalid config file" cacert.errtxt="File not defined" cacertcontents.errtxt="" + cakey.errtxt="File not defined" else - --cacert.value = getconfigpath(config.ca.default_ca, "private_key") cacert.value = getconfigpath(config.ca.default_ca, "certificate") if not fs.is_file(cacert.value) then cacert.errtxt="File not found" @@ -186,23 +203,17 @@ getstatus = function() cacertcontents.value = f:read("*a") f:close() end + cakey.value = getconfigpath(config.ca.default_ca, "private_key") + if not fs.is_file(cakey.value) then + cakey.errtxt="File not found" + end end end - return cfe({ type="group", value={version=version, conffile=conffile, cacert=cacert, cacertcontents=cacertcontents}, label="openssl status" }) + return cfe({ type="group", value={version=version, conffile=conffile, cacert=cacert, cacertcontents=cacertcontents, cakey=cakey}, label="openssl status" }) end getreqdefaults = function() - local defaults = cfe({ type="group", value={} }) - config = config or getopts.getoptsfromfile(configfile) - local distinguished_name = config.req.distinguished_name or "" - - -- Get the distinguished name defaults - for i, name in ipairs(distinguished_names) do - defaults.value[name.name] = cfe({ label=name.label, - value=config[distinguished_name][name.name .. "_default"] - or config[distinguished_name]["0."..name.name.."_default"] or "", - descr=config[distinguished_name][name.name] or config[distinguished_name]["0."..name.name] }) - end + local defaults = getdefaults() -- Add in the ca type default defaults.value.certtype = cfe({ type="select", label="Certificate Type", @@ -264,10 +275,6 @@ submitrequest = function(clientdata, user) end if success then - -- FIXME check to make sure same certificate or request doesn't already exist - end - - if success then -- Submit the request local reqname = requestdir..user.."."..defaults.value.certtype.value.."."..defaults.value.commonName.value local subject = create_subject_string(defaults) @@ -276,7 +283,11 @@ submitrequest = function(clientdata, user) local cmdresult = f:read("*a") f:close() defaults.descr = cmdresult - if fs.is_file(reqname..".csr") then + local certfilestats = posix.stat(reqname..".csr") + local keyfilestats = posix.stat(reqname..".pem") + if not certfilestats or certfilestats.size == 0 or not keyfilestats or keyfilestats.size == 0 then + success = false + else fs.write_file(reqname..".pwd", defaults.value.password.value) end end @@ -329,9 +340,10 @@ approverequest = function(request) local f = io.popen(cmd) cmdresult.value = f:read("*a") f:close() - + -- If certificate created, create the wrapped up pkcs12 - if fs.is_file(certname..".crt") then + local filestats = posix.stat(certname..".crt") + if filestats and filestats.size > 0 then cmd = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin openssl pkcs12 -export -inkey "..path..".pem -in "..certname..".crt -out "..certname..".pfx -passout file:"..path..".pwd 2>&1" f = io.popen(cmd) local newcmdresult = f:read("*a") @@ -340,10 +352,19 @@ approverequest = function(request) end -- Finally, remove the request - if fs.is_file(certname..".pfx") then + filestats = posix.stat(certname..".pfx") + if filestats and filestats.size > 0 then + cmd = "cp "..path..".pwd "..certname..".pwd" + f = io.popen(cmd) + f:close() cmd = "rm "..path..".*" f = io.popen(cmd) f:close() + else + -- or failed, remove the cert + cmd = "rm "..certname..".*" + f = io.popen(cmd) + f:close() end end return cmdresult @@ -374,7 +395,10 @@ listcerts = function(user) end viewcert = function(cert) - local cmdresult = fs.read_file(certdir..cert..".crt") + local cmd = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin openssl x509 -in "..certdir..cert..".crt -noout -text" + local f = io.popen(cmd) + local cmdresult = f:read("*a") + f:close() local a,b,c,d = string.match(cert, "([^%.]*)%.([^%.]*)%.([^%.]*).([^%.]*)") return cfe({ type="table", value={name=name, user=a, certtype=b, commonName=c, serial=d, value=cmdresult}, label="Certificate" }) end @@ -382,7 +406,6 @@ end getcert = function(cert) local f = fs.read_file(certdir..cert..".pfx") return cfe({ type="raw", value=f, label=cert..".pfx", option="application/x-pkcs12" }) - --return cfe({ type="raw", value=f, label=cert..".pfx" }) end revokecert = function(cert) @@ -435,20 +458,116 @@ getcrl = function(crltype) return crlfile end --- FIXME this won't work because haserl doesn't support file upload. Untested and unfinished putca = function(file, pword, set) local ca = cfe({ type="raw", value=0, label="CA Certificate", descr='File must be a password protected ".pfx" file' }) local password = cfe({ label="Certificate Password" }) local retval = cfe({ type="group", value={ca=ca, password=password} }) if file and pword and set then - fs.write_file(openssldir.."temp.pfx", file) - fs.write_file(openssldir.."temp.pwd", pword) + local success = true + -- Trying to upload a cert/key + -- The way haserl works, file contains the temporary file name + -- First, get the cert + local cmd = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin openssl pkcs12 -in "..file.." -out "..file.."cert.pem -password pass:"..pword.." -nokeys 2>&1" + local f = io.popen(cmd) + local cmdresult = f:read("*a") + f:close() + local filestats = posix.stat(file.."cert.pem") + if not filestats or filestats.size == 0 then + ca.errtxt = "Could not open certificate\n"..cmdresult + success = false + end + + -- Since -cacerts doesn't seem to work, we have to check to make sure we got a CA + if success then + cmd = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin openssl x509 -in "..file.."cert.pem -noout -text" + f = io.popen(cmd) + cmdresult = f:read("*a") + f:close() + if not string.find(cmdresult, "CA:TRUE") then + ca.errtxt = "Could not find CA Certificate" + success = false + end + end - -- Still need to verify input (using openssl pkcs12) and put cert and key in right place + -- Now, get the key + if success then + cmd = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin openssl pkcs12 -in "..file.." -out "..file.."key.pem -password pass:"..pword.." -nocerts -nodes 2>&1" + f = io.popen(cmd) + cmdresult = f:read("*a") + f:close() + filestats = posix.stat(file.."key.pem") + if not filestats or filestats.size == 0 then + ca.errtxt = "Could not find CA key\n"..cmdresult + success = false + end + end + + if success then + -- copy the keys + copyca(file.."cert.pem", file.."key.pem") + else + retval.errtxt = "Failed to upload CA certificate" + end + + -- Delete the temporary files + cmd = "rm "..file.."*" + f = io.popen(cmd) + f:close() end return retval end +getnewcarequest = function() + request = getdefaults() + -- In addition to the distinguished name defaults, we need days + request.value.days = cfe({ value="365", label="Number of days to certify" }) + return request +end + +generateca = function(clientdata) + -- First, get the defaults + local defaults = getnewcarequest() + + -- Then, copy in user values and validate + local success, defaults = handle_req_clientdata(clientdata, defaults) + + if not validator.is_integer(defaults.value.days.value) then + defaults.value.days.errtxt = "Must be a number" + success = false + end + + if success then + -- Submit the request + local subject = create_subject_string(defaults) + local cmd = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin openssl req -x509 -nodes -new -config "..configfile.." -keyout /tmp/cakey.pem -out /tmp/cacert.pem -subj '"..subject.."' -days "..defaults.value.days.value.." 2>&1" + local f = io.popen(cmd) + local cmdresult = f:read("*a") + f:close() + local certfilestats = posix.stat("/tmp/cacert.pem") + local keyfilestats = posix.stat("/tmp/cakey.pem") + if not certfilestats or certfilestats.size == 0 or not keyfilestats or keyfilestats.size == 0 then + defaults.errtxt = "Failed to generate CA certificate\n"..cmdresult + success = false + end + + if success then + -- copy the keys + copyca("/tmp/cacert.pem", "/tmp/cakey.pem") + end + + -- Delete the temporary files + cmd = "rm /tmp/ca*.pem" + f = io.popen(cmd) + f:close() + end + + if not success and not defaults.errtxt then + defaults.errtxt = "Failed to generate CA certificate" + end + + return defaults +end + getconfigfile = function() local filename = cfe({ value=configfile, label="File Name" }) local filecontent = cfe({ type="longtext", label="Config file" }) diff --git a/openssl-putcacert-html.lsp b/openssl-putcacert-html.lsp index 2707c09..ed3ab02 100644 --- a/openssl-putcacert-html.lsp +++ b/openssl-putcacert-html.lsp @@ -7,7 +7,7 @@ io.write(html.cfe_unpack(form)) <H1><?= form.label ?></H1> <? - form.action = '" enctype="multipart/form-data' + form.action = 'putcacert" enctype="multipart/form-data' form.value.ca.type="file" form.value.password.type="password" local order = {"ca", "password"} diff --git a/openssl-status-html.lsp b/openssl-status-html.lsp index a49b7ac..37d45fc 100644 --- a/openssl-status-html.lsp +++ b/openssl-status-html.lsp @@ -1,4 +1,4 @@ -<? local view, viewlibrary = ... ?> +<? local view, viewlibrary, pageinfo, session = ... ?> <? require("viewfunctions") ?> <? --[[ DEBUG INFORMATION @@ -10,5 +10,19 @@ io.write(html.cfe_unpack(view)) <? displayitem(view.value.version) ?> <? displayitem(view.value.conffile) ?> <? displayitem(view.value.cacert) ?> -<? displayitem(view.value.cacertcontents) ?> +<? displayitem(view.value.cakey) ?> </DL> + +<? if not view.value.cacert.errtxt and not view.value.cakey.errtxt then ?> +<H1>CA Certificate contents</H1> +<pre><?= view.value.cacertcontents.value ?></pre> +<? elseif not view.value.version.errtxt and not view.value.conffile.errtxt then + if viewlibrary and viewlibrary.dispatch_component then + if session.permissions.openssl.putcacert then + viewlibrary.dispatch_component("putcacert") + end + if session.permissions.openssl.generatecacert then + viewlibrary.dispatch_component("generatecacert") + end + end +end ?> diff --git a/openssl-viewcert-html.lsp b/openssl-viewcert-html.lsp index b8d6136..0593afd 100644 --- a/openssl-viewcert-html.lsp +++ b/openssl-viewcert-html.lsp @@ -5,4 +5,4 @@ io.write(html.cfe_unpack(view)) --]] ?> <H1>Certificate Details</H1> -<?= string.gsub(view.value.value, "\n", "<br>") ?> +<pre><?= view.value.value ?></pre> diff --git a/openssl-viewrequest-html.lsp b/openssl-viewrequest-html.lsp index ace59b7..cea358e 100644 --- a/openssl-viewrequest-html.lsp +++ b/openssl-viewrequest-html.lsp @@ -5,4 +5,4 @@ io.write(html.cfe_unpack(view)) --]] ?> <H1>Request Details</H1> -<?= string.gsub(view.value.value, "\n", "<br>") ?> +<pre><?= view.value.value ?></pre> diff --git a/openssl.roles b/openssl.roles index 43ebed8..1e7896a 100644 --- a/openssl.roles +++ b/openssl.roles @@ -1,2 +1,2 @@ READ=openssl:status,openssl:read,openssl:request,openssl:viewrequest,openssl:deletemyrequest,openssl:viewcert,openssl:getcert,openssl:getrevoked -UPDATE=openssl:editdefaults,openssl:readall,openssl:approve,openssl:deleterequest,openssl:revoke,openssl:deletecert,openssl:putcacert,openssl:editconfigfile +UPDATE=openssl:editdefaults,openssl:readall,openssl:approve,openssl:deleterequest,openssl:revoke,openssl:deletecert,openssl:putcacert,openssl:generatecacert,openssl:editconfigfile |