mirror of
https://git.openwrt.org/project/luci.git
synced 2025-11-01 06:21:40 +08:00
luci-proto-openconnect: bug fixes for cert read and write methods
follow-up to: aa955d6465
Minor refactor of ucode, and some GUI fixes to ensure certificates are
written properly.
Signed-off-by: Paul Donald <newtwen+github@gmail.com>
This commit is contained in:
@ -4,14 +4,14 @@
|
||||
'require network';
|
||||
'require validation';
|
||||
|
||||
var callGetCertificateFiles = rpc.declare({
|
||||
const callGetCertificateFiles = rpc.declare({
|
||||
object: 'luci.openconnect',
|
||||
method: 'getCertificates',
|
||||
params: [ 'interface' ],
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
var callSetCertificateFiles = rpc.declare({
|
||||
const callSetCertificateFiles = rpc.declare({
|
||||
object: 'luci.openconnect',
|
||||
method: 'setCertificates',
|
||||
params: [ 'interface', 'user_certificate', 'user_privatekey', 'ca_certificate' ],
|
||||
@ -22,14 +22,14 @@ network.registerPatternVirtual(/^vpn-.+$/);
|
||||
|
||||
function sanitizeCert(s) {
|
||||
if (typeof(s) != 'string')
|
||||
return null;
|
||||
return '';
|
||||
|
||||
s = s.trim();
|
||||
|
||||
if (s == '')
|
||||
return null;
|
||||
return s;
|
||||
|
||||
s = s.replace(/\r\n?/g, '\n');
|
||||
s = s.replace(/\r?\n/g, '\n');
|
||||
|
||||
if (!s.match(/\n$/))
|
||||
s += '\n';
|
||||
@ -38,24 +38,22 @@ function sanitizeCert(s) {
|
||||
}
|
||||
|
||||
function validateCert(priv, section_id, value) {
|
||||
var beg = priv ? /^-----BEGIN (RSA )?PRIVATE KEY-----$/ : /^-----BEGIN CERTIFICATE-----$/,
|
||||
end = priv ? /^-----END (RSA )?PRIVATE KEY-----$/ : /^-----END CERTIFICATE-----$/,
|
||||
lines = value.trim().split(/[\r\n]/),
|
||||
start = false,
|
||||
i;
|
||||
|
||||
if (value === null || value === '')
|
||||
if (!value?.trim())
|
||||
return true;
|
||||
|
||||
for (i = 0; i < lines.length; i++) {
|
||||
if (lines[i].match(beg))
|
||||
start = true;
|
||||
else if (start && !lines[i].match(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/))
|
||||
break;
|
||||
}
|
||||
const beg = priv ? /^-----BEGIN (RSA )?PRIVATE KEY-----$/ : /^-----BEGIN CERTIFICATE-----$/;
|
||||
const end = priv ? /^-----END (RSA )?PRIVATE KEY-----$/ : /^-----END CERTIFICATE-----$/;
|
||||
const lines = value.trim().split(/[\r?\n]/);
|
||||
const base64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
||||
const errmsg = _('This does not look like a valid PEM file');
|
||||
|
||||
if (!start || i < lines.length - 1 || !lines[i].match(end))
|
||||
return _('This does not look like a valid PEM file');
|
||||
|
||||
if (!lines?.[0].match(beg) || !lines.at(-1).match(end))
|
||||
return errmsg;
|
||||
|
||||
for (let i = 1; i < lines.length - 1; i++)
|
||||
if (!base64.test(lines[i]))
|
||||
return errmsg;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -90,9 +88,9 @@ return network.registerProtocol('openconnect', {
|
||||
},
|
||||
|
||||
renderFormOptions: function(s) {
|
||||
var dev = this.getDevice().getName(),
|
||||
certLoadPromise = null,
|
||||
o;
|
||||
const dev = this.getDevice().getName();
|
||||
let certLoadPromise = null;
|
||||
let o;
|
||||
|
||||
o = s.taboption('general', form.ListValue, 'vpn_protocol', _('VPN Protocol'));
|
||||
o.value('anyconnect', 'OpenConnect or Cisco AnyConnect SSL VPN');
|
||||
@ -106,7 +104,7 @@ return network.registerProtocol('openconnect', {
|
||||
o = s.taboption('general', form.Value, 'uri', _('VPN Server'));
|
||||
o.placeholder = 'https://example.com:443/usergroup';
|
||||
o.validate = function(section_id, value) {
|
||||
var m = String(value).match(/^(?:(\w+):\/\/|)(?:\[([0-9a-f:.]{2,45})\]|([^\/:]+))(?::([0-9]{1,5}))?(?:\/.*)?$/i);
|
||||
const m = String(value).match(/^(?:(\w+):\/\/|)(?:\[([0-9a-f:.]{2,45})\]|([^\/:]+))(?::([0-9]{1,5}))?(?:\/.*)?$/i);
|
||||
|
||||
if (!m)
|
||||
return _('Invalid server URL');
|
||||
@ -163,7 +161,7 @@ return network.registerProtocol('openconnect', {
|
||||
return certLoadPromise.then(function(certs) { return certs.user_certificate });
|
||||
};
|
||||
o.write = function(section_id, value) {
|
||||
return callSetCertificateFiles(section_id, sanitizeCert(value), null, null);
|
||||
return callSetCertificateFiles(section_id, sanitizeCert(value), '', '');
|
||||
};
|
||||
|
||||
o = s.taboption('general', form.TextValue, 'userkey', _('User key (PEM encoded)'));
|
||||
@ -175,7 +173,7 @@ return network.registerProtocol('openconnect', {
|
||||
return certLoadPromise.then(function(certs) { return certs.user_privatekey });
|
||||
};
|
||||
o.write = function(section_id, value) {
|
||||
return callSetCertificateFiles(section_id, null, sanitizeCert(value), null);
|
||||
return callSetCertificateFiles(section_id, '', sanitizeCert(value), '');
|
||||
};
|
||||
|
||||
o = s.taboption('general', form.TextValue, 'ca', _('CA certificate; if empty it will be saved after the first connection.'));
|
||||
@ -187,7 +185,7 @@ return network.registerProtocol('openconnect', {
|
||||
return certLoadPromise.then(function(certs) { return certs.ca_certificate });
|
||||
};
|
||||
o.write = function(section_id, value) {
|
||||
return callSetCertificateFiles(section_id, null, null, sanitizeCert(value));
|
||||
return callSetCertificateFiles(section_id, '', '', sanitizeCert(value));
|
||||
};
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU'));
|
||||
|
||||
@ -1,41 +1,32 @@
|
||||
#!/usr/bin/env ucode
|
||||
|
||||
'use strict';
|
||||
|
||||
import { readfile, writefile, stat } from 'fs';
|
||||
|
||||
const interfaceregex = /^[a-zA-Z0-9_]+$/;
|
||||
const user_certificate_string = "/etc/openconnect/user-cert-vpn-%s.pem";
|
||||
const user_privatekey_string = "/etc/openconnect/user-key-vpn-%s.pem";
|
||||
const ca_certificate_string = "/etc/openconnect/ca-vpn-%s.pem";
|
||||
const paths = {
|
||||
user_certificate: "/etc/openconnect/user-cert-vpn-%s.pem",
|
||||
user_privatekey: "/etc/openconnect/user-key-vpn-%s.pem",
|
||||
ca_certificate: "/etc/openconnect/ca-vpn-%s.pem"
|
||||
};
|
||||
|
||||
|
||||
// Utility to read a file
|
||||
function _readfile(path) {
|
||||
let _stat = stat(path);
|
||||
if (_stat && _stat.type == "file") {
|
||||
let content = readfile(path);
|
||||
return content ? trim(content) : 'File empty';
|
||||
}
|
||||
return 'File not found';
|
||||
let s = stat(path);
|
||||
return (s?.type == 'file') ? trim(readfile(path) ?? '') || 'File empty' : null;
|
||||
}
|
||||
|
||||
// Utility to write a file
|
||||
function _writefile(path, data) {
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
return writefile(path, data) == length(data);
|
||||
return data ? writefile(path, data) == length(data) : false;
|
||||
}
|
||||
|
||||
function is_valid_iface(ifname) {
|
||||
return ifname && match(ifname, interfaceregex);
|
||||
}
|
||||
|
||||
const methods = {
|
||||
|
||||
list:{
|
||||
list: {
|
||||
call: function() {
|
||||
return {
|
||||
getCertificates: {
|
||||
interface: "interface"
|
||||
},
|
||||
getCertificates: { interface: "interface" },
|
||||
setCertificates: {
|
||||
interface: "interface",
|
||||
user_certificate: "user_certificate",
|
||||
@ -47,29 +38,16 @@ const methods = {
|
||||
},
|
||||
|
||||
getCertificates: {
|
||||
args: {
|
||||
interface: "interface",
|
||||
},
|
||||
args: { interface: "interface" },
|
||||
call: function(req) {
|
||||
let iface = req.args?.interface;
|
||||
if (!is_valid_iface(iface)) return;
|
||||
|
||||
const _interface = req.args?.interface;
|
||||
if (!_interface || !match(_interface, interfaceregex)) {
|
||||
// printf("Invalid interface name");
|
||||
return;
|
||||
}
|
||||
|
||||
const user_certificate_pem = _readfile(sprintf(user_certificate_string, _interface));
|
||||
const user_privatekey_pem = _readfile(sprintf(user_privatekey_string, _interface));
|
||||
const ca_certificate_pem = _readfile(sprintf(ca_certificate_string, _interface));
|
||||
|
||||
if(user_certificate_pem && user_privatekey_pem && ca_certificate_pem){
|
||||
return {
|
||||
user_certificate: user_certificate_pem,
|
||||
user_privatekey: user_privatekey_pem,
|
||||
ca_certificate: ca_certificate_pem,
|
||||
};
|
||||
}
|
||||
let result = {};
|
||||
for (let k in paths)
|
||||
result[k] = _readfile(sprintf(paths[k], iface));
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
@ -81,35 +59,17 @@ const methods = {
|
||||
ca_certificate: "ca_certificate",
|
||||
},
|
||||
call: function(req) {
|
||||
let iface = req.args?.interface;
|
||||
if (!is_valid_iface(iface)) return;
|
||||
|
||||
let result = false;
|
||||
let _interface = req.args?.interface;
|
||||
|
||||
if (!_interface || !match(_interface, interfaceregex)) {
|
||||
// printf("Invalid interface name");
|
||||
return;
|
||||
for (let k in paths) {
|
||||
if (req.args?.[k])
|
||||
result = _writefile(sprintf(paths[k], iface), req.args[k]);
|
||||
}
|
||||
|
||||
/* the interface is set up to call 1 write per certificate,
|
||||
with only one of the following arguments not null */
|
||||
if (req.args?.user_certificate) {
|
||||
result = _writefile(sprintf(user_certificate_string, _interface), req.args?.user_certificate);
|
||||
}
|
||||
if (req.args?.user_privatekey) {
|
||||
result = _writefile(sprintf(user_privatekey_string, _interface), req.args?.user_privatekey);
|
||||
}
|
||||
if (req.args?.ca_certificate) {
|
||||
result = _writefile(sprintf(ca_certificate_string, _interface), req.args?.ca_certificate);
|
||||
}
|
||||
|
||||
return {
|
||||
result: result,
|
||||
};
|
||||
|
||||
return { result: result };
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return { 'luci.openconnect': methods };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user