diff options
Diffstat (limited to 'openssl-model.lua')
-rw-r--r-- | openssl-model.lua | 223 |
1 files changed, 171 insertions, 52 deletions
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" }) |