summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsarthurdev <965089+sarthurdev@users.noreply.github.com>2022-06-25 23:04:31 +0200
committersarthurdev <965089+sarthurdev@users.noreply.github.com>2022-06-29 01:37:59 +0200
commit0d5ac59894ae7c10bd9d69047fa7098de66f835f (patch)
tree2b36fb68c41ddb4f13a087833edd774cd841f005
parent56457c9f35c5273d1d7ad679f37278c6cf2c77b0 (diff)
downloadvyos-1x-0d5ac59894ae7c10bd9d69047fa7098de66f835f.tar.gz
vyos-1x-0d5ac59894ae7c10bd9d69047fa7098de66f835f.zip
openvpn: T4485: Accept multiple `tls ca-certificate` values
-rw-r--r--interface-definitions/include/pki/ca-certificate-multi.xml.i15
-rw-r--r--interface-definitions/interfaces-openvpn.xml.in2
-rw-r--r--python/vyos/pki.py32
-rw-r--r--python/vyos/util.py4
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py43
5 files changed, 79 insertions, 17 deletions
diff --git a/interface-definitions/include/pki/ca-certificate-multi.xml.i b/interface-definitions/include/pki/ca-certificate-multi.xml.i
new file mode 100644
index 000000000..646131b54
--- /dev/null
+++ b/interface-definitions/include/pki/ca-certificate-multi.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from pki/ca-certificate-multi.xml.i -->
+<leafNode name="ca-certificate">
+ <properties>
+ <help>Certificate Authority chain in PKI configuration</help>
+ <completionHelp>
+ <path>pki ca</path>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of CA in PKI configuration</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in
index f1cbf8468..6cbd91ff4 100644
--- a/interface-definitions/interfaces-openvpn.xml.in
+++ b/interface-definitions/interfaces-openvpn.xml.in
@@ -741,7 +741,7 @@
</properties>
</leafNode>
#include <include/pki/certificate.xml.i>
- #include <include/pki/ca-certificate.xml.i>
+ #include <include/pki/ca-certificate-multi.xml.i>
<leafNode name="dh-params">
<properties>
<help>Diffie Hellman parameters (server only)</help>
diff --git a/python/vyos/pki.py b/python/vyos/pki.py
index fd91fc9bf..648064a3a 100644
--- a/python/vyos/pki.py
+++ b/python/vyos/pki.py
@@ -332,6 +332,25 @@ def verify_certificate(cert, ca_cert):
except InvalidSignature:
return False
+def verify_ca_chain(sorted_names, pki_node):
+ if len(sorted_names) == 1: # Single cert, no chain
+ return True
+
+ for name in sorted_names:
+ cert = load_certificate(pki_node[name]['certificate'])
+ verified = False
+ for ca_name in sorted_names:
+ if name == ca_name:
+ continue
+ ca_cert = load_certificate(pki_node[ca_name]['certificate'])
+ if verify_certificate(cert, ca_cert):
+ verified = True
+ break
+ if not verified and name != sorted_names[-1]:
+ # Only permit top-most certificate to fail verify (e.g. signed by public CA not explicitly in chain)
+ return False
+ return True
+
# Certificate chain
def find_parent(cert, ca_certs):
@@ -357,3 +376,16 @@ def find_chain(cert, ca_certs):
chain.append(parent)
return chain
+
+def sort_ca_chain(ca_names, pki_node):
+ def ca_cmp(ca_name1, ca_name2, pki_node):
+ cert1 = load_certificate(pki_node[ca_name1]['certificate'])
+ cert2 = load_certificate(pki_node[ca_name2]['certificate'])
+
+ if verify_certificate(cert1, cert2): # cert1 is child of cert2
+ return -1
+ return 1
+
+ from functools import cmp_to_key
+ return sorted(ca_names, key=cmp_to_key(lambda cert1, cert2: ca_cmp(cert1, cert2, pki_node)))
+
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 0d62fbfe9..bee5d7aec 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -197,7 +197,7 @@ def read_file(fname, defaultonfailure=None):
return defaultonfailure
raise e
-def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=None):
+def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=None, append=False):
"""
Write content of data to given fname, should defaultonfailure be not None,
it is returned on failure to read.
@@ -212,7 +212,7 @@ def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=N
try:
""" Write a file to string """
bytes = 0
- with open(fname, 'w') as f:
+ with open(fname, 'w' if not append else 'a') as f:
bytes = f.write(data)
chown(fname, user, group)
chmod(fname, mode)
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 4750ca3e8..280a62b9a 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -39,6 +39,8 @@ from vyos.configverify import verify_mirror_redirect
from vyos.ifconfig import VTunIf
from vyos.pki import load_dh_parameters
from vyos.pki import load_private_key
+from vyos.pki import sort_ca_chain
+from vyos.pki import verify_ca_chain
from vyos.pki import wrap_certificate
from vyos.pki import wrap_crl
from vyos.pki import wrap_dh_parameters
@@ -148,8 +150,14 @@ def verify_pki(openvpn):
if 'ca_certificate' not in tls:
raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface}')
- if tls['ca_certificate'] not in pki['ca']:
- raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}')
+ for ca_name in tls['ca_certificate']:
+ if ca_name not in pki['ca']:
+ raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}')
+
+ if len(tls['ca_certificate']) > 1:
+ sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca'])
+ if not verify_ca_chain(sorted_chain, pki['ca']):
+ raise ConfigError(f'CA certificates are not a valid chain')
if mode != 'client' and 'auth_key' not in tls:
if 'certificate' not in tls:
@@ -516,21 +524,28 @@ def generate_pki_files(openvpn):
if tls:
if 'ca_certificate' in tls:
- cert_name = tls['ca_certificate']
- pki_ca = pki['ca'][cert_name]
+ cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem')
+ crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem')
- if 'certificate' in pki_ca:
- cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem')
- write_file(cert_path, wrap_certificate(pki_ca['certificate']),
- user=user, group=group, mode=0o600)
+ if os.path.exists(cert_path):
+ os.unlink(cert_path)
+
+ if os.path.exists(crl_path):
+ os.unlink(crl_path)
+
+ for cert_name in sort_ca_chain(tls['ca_certificate'], pki['ca']):
+ pki_ca = pki['ca'][cert_name]
+
+ if 'certificate' in pki_ca:
+ write_file(cert_path, wrap_certificate(pki_ca['certificate']) + "\n",
+ user=user, group=group, mode=0o600, append=True)
- if 'crl' in pki_ca:
- for crl in pki_ca['crl']:
- crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem')
- write_file(crl_path, wrap_crl(crl), user=user, group=group,
- mode=0o600)
+ if 'crl' in pki_ca:
+ for crl in pki_ca['crl']:
+ write_file(crl_path, wrap_crl(crl) + "\n", user=user, group=group,
+ mode=0o600, append=True)
- openvpn['tls']['crl'] = True
+ openvpn['tls']['crl'] = True
if 'certificate' in tls:
cert_name = tls['certificate']