diff options
-rw-r--r-- | openssl-controller.lua | 30 | ||||
-rw-r--r-- | openssl-editdefaults-html.lsp | 4 | ||||
-rw-r--r-- | openssl-html.lsp | 29 | ||||
-rw-r--r-- | openssl-model.lua | 232 | ||||
-rw-r--r-- | openssl-request-html.lsp | 7 | ||||
-rw-r--r-- | openssl.menu | 10 | ||||
-rw-r--r-- | openssl.roles | 3 |
7 files changed, 282 insertions, 33 deletions
diff --git a/openssl-controller.lua b/openssl-controller.lua index 88bb098..c359b51 100644 --- a/openssl-controller.lua +++ b/openssl-controller.lua @@ -7,15 +7,20 @@ default_action = "read" -- View all pending and approved requests and revoked certificates readall = function(self) + local pending = self.model.listrequests() + local approved = nil + local revoked = nil + local result = cfe({ type="list", value={pending=pending, approved=approved, revoked=revoked} }) + return result end -- Return all certificates (pending, approved, and revoked) for this user read = function(self) - local user = cfe({ value="Ted", label="User Name" }) - local result = cfe({ type="list", value={user=user} }) --- result.value[1] = getopts.getoptsfromfile(configfile, nil, nil) --- getopts.setoptsinfile(configfile, "Hey", "test", "--qasdg", true, nil) --- result.value[2] = getopts.getoptsfromfile(configfile, nil, nil) + local user = cfe({ value=self.sessiondata.userinfo.userid, label="User Name" }) + local pending = self.model.listrequests(self.sessiondata.userinfo.userid) + local approved = nil + local revoked = nil + local result = cfe({ type="list", value={user=user, pending=pending, approved=approved, revoked=revoked} }) return result end @@ -24,9 +29,13 @@ request = function(self) local request if self.clientdata.Submit then -- Try to submit the request - request = cfe({ type="group", value={} }) + request = self.model.submitrequest(self.clientdata, self.sessiondata.userinfo.userid) + if not request.errtxt then + request.descr = "Submitted request" + --redirect(self) + end else - request = self.model.getdefaults() + request = self.model.getnewrequest() end request.type = "form" @@ -40,9 +49,12 @@ end editdefaults = function(self) local defaults if self.clientdata.Save then - defaults = cfe({ type="group", value={} }) + defaults = self.model.setreqdefaults(self.clientdata) + if not defaults.errtxt then + defaults.descr = "Defaults set" + end else - defaults = self.model.getdefaults() + defaults = self.model.getreqdefaults() end defaults.type = "form" diff --git a/openssl-editdefaults-html.lsp b/openssl-editdefaults-html.lsp index bc153a4..46bc44a 100644 --- a/openssl-editdefaults-html.lsp +++ b/openssl-editdefaults-html.lsp @@ -7,6 +7,8 @@ io.write(html.cfe_unpack(form)) <H1><?= form.label ?></H1> <? - displayform(form) + local order = { "countryName", "stateOrProvinceName", "localityName", "organizationName", + "organizationalUnitName", "commonName", "emailAddress", "certtype" } + displayform(form, order) ?> diff --git a/openssl-html.lsp b/openssl-html.lsp index 60f659a..54b4f57 100644 --- a/openssl-html.lsp +++ b/openssl-html.lsp @@ -7,18 +7,43 @@ io.write(html.cfe_unpack(view)) <H1>Pending certificate requests<? if view.value.user then?> for <?= view.value.user.value ?><? end ?></H1> <? if not view.value.pending or #view.value.pending.value == 0 then ?> No certificates pending -<? else -end ?> +<? else ?> +<TABLE> + <TR style="background:#eee;font-weight:bold;"> + <? if session.permissions.openssl.approve then ?> + <TD style="padding-right:20px;white-space:nowrap;" class="header">Approve</TD> + <? end ?> + <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="white-space:nowrap;" class="header">Common Name</TD> + <? for i,request in ipairs(view.value.pending.value) do ?> + <TR> + <? if session.permissions.openssl.approve then ?> + <TD><?= html.link{value="approve?request="..request.name, label="Approve"} ?></TD> + <? end ?> + <TD><?= request.user ?></TD> + <TD><?= request.certtype ?></TD> + <TD><?= request.commonName ?></TD> + </TR> + <? end ?> +</TABLE> +<? end ?> <H1>Approved certificate requests<? if view.value.user then?> for <?= view.value.user.value ?><? end ?></H1> <? if not view.value.approved or #view.value.approved.value == 0 then ?> No certificates approved <? else + for i,name in ipairs(view.value.approved.value) do + io.write(name..'<br>') + end end ?> <H1>Revoked certificates<? if view.value.user then?> for <?= view.value.user.value ?><? end ?></H1> <? if not view.value.revoked or #view.value.revoked.value == 0 then ?> No certificates revoked <? else + for i,name in ipairs(view.value.revoked.value) do + io.write(name..'<br>') + end end ?> diff --git a/openssl-model.lua b/openssl-model.lua index bbb5f60..a80ade4 100644 --- a/openssl-model.lua +++ b/openssl-model.lua @@ -1,36 +1,240 @@ module(..., package.seeall) +require("html") + +-- 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 +-- different ca sections for the different types of certificates. The ca section to use when signing the request is +-- actually stored in the request filename. The request filename is in the following format: +-- 'username'.'ca section name'.'common name'.csr + local configfile = "/etc/ssl/openssl.cnf" +local requestdir = "/etc/ssl/req/" -- list of request entries that can be edited -local distinguished_names = { "countryName", "stateOrProvinceName", "localityName", "organizationName", "organizationalUnitName", "commonName", "emailAddress" } +local distinguished_names = { {name="countryName", label="Country Name", short="C"}, + {name="stateOrProvinceName", label="State Or Province Name", short="ST"}, + {name="localityName", label="Locality Name", short="L"}, + {name="organizationName", label="Organization Name", short="O"}, + {name="organizationalUnitName", label="Organizational Unit Name", short="OU"}, + {name="commonName", label="Common Name", short="CN"}, + {name="emailAddress", label="e-mail Address"} } +-- list of entries that may be found in cert extensions section +local extensions = { "basicConstraints", "nsCertType", "nsComment", "keyUsage", "subjectKeyIdentifier", + "authorityKeyIdentifier", "subjectAltName", "issuerAltName" } +-- list of entries that must be found in ca section +local ca_mandatory_entries = { "new_certs_dir", "certificate", "private_key", "default_md", "database", "policy" } -local validate_distinguished_names = function(clientdata) - local config = getopts.getoptsfromfile(configfile) +-- Validate the values of distinguished names using the min/max found in the config file +local validate_distinguished_names = function(values, inputconfig) + local config = inputconfig or getopts.getoptsfromfile(configfile) local distinguished_name = config.req.distinguished_name or "" + local success = true for i, name in ipairs(distinguished_names) do - if config[distinguished_name][name.."_min"] then + local min = config[distinguished_name][name.name.."_min"] or config[distinguished_name]["0."..name.name.."_min"] + if min and values.value[name.name] and #values.value[name.name].value < tonumber(min) then + values.value[name.name].errtxt = "Value too short" + success = false + end + local max = config[distinguished_name][name.name.."_max"] or config[distinguished_name]["0."..name.name.."_max"] + if max and values.value[name.name] and #values.value[name.name].value > tonumber(max) then + values.value[name.name].errtxt = "Value too long" + success = false end - end + end + return success, values end -getdefaults = function() +-- Write distinguished name defaults to config file +local write_distinguished_names = function(values, inputconfig) + local file = fs.read_file(configfile) + local config = inputconfig or getopts.getoptsfromfile(file) + local distinguished_name = config.req.distinguished_name or "" + + for i,name in ipairs(distinguished_names) do + wname = name.name.."_default" + if config[distinguished_name]["0."..name.name] then + wname = "0."..wname + end + if values.value[name.name] then + local a,b,c + a,b,c, file = getopts.setoptsinfile(file, distinguished_name, wname, values.value[name.name].value) + end + end + fs.write_file(configfile, file) +end + +local create_subject_string = function(values) + local outstr = {} + for i,name in ipairs(distinguished_names) do + outstr[#outstr + 1] = (name.short or name.name) .. "=" .. values.value[name.name].value + end + outstr[#outstr + 1] = "password="..values.value.password.value + return "/"..table.concat(outstr, "/") +end + +-- Find the sections of the config file that define ca's (ca -name option) +local find_ca_sections = function(inputconfig) + local config = inputconfig or getopts.getoptsfromfile(configfile) + local cert_types = {} + + for section in pairs(config) do + local success = true + for i,entry in ipairs(ca_mandatory_entries) do + if not config[section][entry] then + success = false + break + end + end + if success then + cert_types[#cert_types + 1] = section + end + end + + return cert_types +end + +local handle_req_clientdata = function(clientdata, defaults, config) + + -- Next, put the user values into the table + for name,value in pairs(clientdata) do + if defaults.value[name] then + defaults.value[name].value = value + end + end + + -- Next, validate the values + local success + success, defaults = validate_distinguished_names(defaults, config) + + 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 + + return success, defaults, config +end + +-- FIXME we need to make sure necessary files / directories / private key are there +verifyopenssl = function() + local retval = false + if fs.is_file(configfile) then + retval=true + end + return retval +end + +getreqdefaults = function(inputconfig) local defaults = cfe({ type="group", value={} }) - local config = getopts.getoptsfromfile(configfile) + local config = inputconfig 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] = cfe({ label=name, - value=config[distinguished_name][name .. "_default"] or "", - descr=config[distinguished_name][name] }) - end + 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 + + -- Add in the ca type default + defaults.value.certtype = cfe({ type="select", label="Certificate Type", + value=config.ca.default_ca, option=find_ca_sections(config) }) + + return defaults +end + +setreqdefaults = function(clientdata) + -- First, get the defaults + local config = getopts.getoptsfromfile(configfile) + local defaults = getreqdefaults() + + -- Then, copy in user values and validate + local success, defaults, config = handle_req_clientdata(clientdata, defaults, config) + + -- Finally, write the values to the config file + if success then + write_distinguished_names(defaults, config) + + getopts.setoptsinfile(configfile, "ca", "default_ca", defaults.value.certtype.value) + end + + if not success then + defaults.errtxt = "Failed to set defaults" + end return defaults end -setdefaults = function(clientdata) - -- validate values - validate_distinguished_names(clientdata) +getnewrequest = function() + local values = getreqdefaults() + -- In addition to the request defaults, we need a password and confirmation + values.value.password = cfe({ label="Password" }) + values.value.password_confirm = cfe({ label="Password confirmation" }) + return values +end + +submitrequest = function(clientdata, user) + -- First, get the defaults + local config = getopts.getoptsfromfile(configfile) + local defaults = getnewrequest(config) + + -- Then, copy in user values and validate + local success, defaults, config = handle_req_clientdata(clientdata, defaults, config) + + -- Must have a common name + if #defaults.value.commonName.value == 0 then + defaults.value.commonName.errtxt = "Common Name cannot be blank" + success = false + end + -- Check validity of password + if #defaults.value.password.value < 4 then + defaults.value.password.errtxt = "Password too short" + success = false + end + if defaults.value.password.value ~= defaults.value.password_confirm.value then + defaults.value.password_confirm.errtxt = "You entered wrong password/confirmation" + success = false + 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) + local cmd = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin openssl req -nodes -new -config "..configfile.." -keyout "..reqname..".pem -out "..reqname..".csr -subj '"..subject.."' 2>&1" + local f = io.popen(cmd) + local cmdresult = f:read("*a") + f:close() + defaults.descr = cmdresult + end + + if not success then + defaults.errtxt = "Failed to submit request" + end + + return defaults +end +listrequests = function(user) + user = user or "" + local list={} + local fh = io.popen('find ' .. requestdir .. ' -name "'..user..'*.csr" -maxdepth 1') + for x in fh:lines() do + local name = basename(x,".csr") + local a,b,c = string.match(name, "([^%.]*)%.([^%.]*)%.([^%.]*)") + list[#list + 1] = {name=name, user=a, certtype=b, commonName=c} + end + return cfe({ type="list", value=list, label="List of pending requests" }) end diff --git a/openssl-request-html.lsp b/openssl-request-html.lsp index bc153a4..ea0655a 100644 --- a/openssl-request-html.lsp +++ b/openssl-request-html.lsp @@ -7,6 +7,11 @@ io.write(html.cfe_unpack(form)) <H1><?= form.label ?></H1> <? - displayform(form) + local order = { "countryName", "stateOrProvinceName", "localityName", "organizationName", + "organizationalUnitName", "commonName", "emailAddress", "certtype", + "password", "password_confirm" } + form.value.password.type = "password" + form.value.password_confirm.type = "password" + displayform(form, order) ?> diff --git a/openssl.menu b/openssl.menu index c3533ca..da1398a 100644 --- a/openssl.menu +++ b/openssl.menu @@ -1,6 +1,6 @@ # Prefix and controller are already known at this point -# Cat Group Tab Action -Applications 10Certificates All readall -Applications 10Certificates Status read -Applications 10Certificates Request request -Applications 10Certificates Edit_Defaults editdefaults +# Cat Group Tab Action +Applications 10Certificate_Authority All readall +Applications 10Certificate_Authority Status read +Applications 10Certificate_Authority Request request +Applications 10Certificate_Authority Edit_Defaults editdefaults diff --git a/openssl.roles b/openssl.roles index 00df7ae..3b4f668 100644 --- a/openssl.roles +++ b/openssl.roles @@ -1 +1,2 @@ -ALL=openssl:read,openssl:request,openssl:editdefaults +READ=openssl:read,openssl:request,openssl:getcert,openssl:getrevoked +UPDATE=openssl:editdefaults,openssl:readall,openssl:approve,openssl:revoke,openssl:putcacert |