summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIndrajit Raychaudhuri <irc@indrajit.com>2026-05-27 14:27:32 -0500
committerIndrajit Raychaudhuri <irc@indrajit.com>2026-06-04 18:33:41 -0500
commitb27b74433a594a3c3b799b31db208da5bac8dd80 (patch)
treef475c8f062118417c6a9f7f5c13397a1457e0428 /src
parent80e1764619b2aa11df69d351a8f149e6784cce10 (diff)
downloadvyos-1x-b27b74433a594a3c3b799b31db208da5bac8dd80.tar.gz
vyos-1x-b27b74433a594a3c3b799b31db208da5bac8dd80.zip
pki: T8165: Add ability to show certificate full chain in pem format
Add op-mode command having ability to show certificate full chain in pem format as part of PKI configuration. The certificates are ordered beginning with the end entity (leaf) certificate, followed by any intermediate certificates and finally the private key if requested. This allows users to easily export a certificate along with its CA hierarchy for use in external applications, that require the full chain to be provided in a single file. One can now run the following commands: ``` show pki ca NAME pem full-chain show pki certificate NAME pem full-chain show pki certificate NAME private pem full-chain ```
Diffstat (limited to 'src')
-rwxr-xr-xsrc/op_mode/pki.py54
1 files changed, 45 insertions, 9 deletions
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index bf8bba657..482dc91af 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -33,6 +33,7 @@ from vyos.pki import encode_certificate
from vyos.pki import encode_public_key
from vyos.pki import encode_private_key
from vyos.pki import encode_dh_parameters
+from vyos.pki import find_chain
from vyos.pki import get_certificate_fingerprint
from vyos.pki import create_certificate
from vyos.pki import create_certificate_request
@@ -84,6 +85,23 @@ def _verify(target):
return _verify_target
+def _encode_certificate_chain(cert, ca_certs):
+ """Encode a certificate together with its parent CA hierarchy into a single
+ PEM-encoded buffer ordered beginning with the end entity (leaf) certificate,
+ followed by the issuer certificates.
+ """
+ loaded_ca_certs = []
+ if ca_certs:
+ for ca_dict in ca_certs.values():
+ if 'certificate' not in ca_dict:
+ continue
+ ca_cert = load_certificate(ca_dict['certificate'])
+ if ca_cert:
+ loaded_ca_certs.append(ca_cert)
+
+ return ''.join(encode_certificate(c) for c in find_chain(cert, loaded_ca_certs))
+
+
def get_default_values():
# Fetch default x509 values
base = ['pki', 'x509', 'default']
@@ -1192,7 +1210,10 @@ def import_pki(
@_verify('ca')
def show_certificate_authority(
- raw: bool, name: typing.Optional[str] = None, pem: typing.Optional[bool] = False
+ raw: bool,
+ name: typing.Optional[str] = None,
+ pem: typing.Optional[bool] = False,
+ full_chain: typing.Optional[bool] = False,
):
headers = [
'Name',
@@ -1214,8 +1235,14 @@ def show_certificate_authority(
cert = load_certificate(cert_dict['certificate'])
+ if not cert:
+ continue
+
if name and pem:
- print(encode_certificate(cert))
+ if full_chain:
+ print(_encode_certificate_chain(cert, certs))
+ else:
+ print(encode_certificate(cert))
return
parent_ca_name = get_certificate_ca(cert, certs)
@@ -1224,9 +1251,6 @@ def show_certificate_authority(
if not parent_ca_name or parent_ca_name == cert_name:
parent_ca_name = 'N/A'
- if not cert:
- continue
-
have_private = (
'Yes'
if 'private' in cert_dict and 'key' in cert_dict['private']
@@ -1254,6 +1278,7 @@ def show_certificate(
name: typing.Optional[str] = None,
private: typing.Optional[bool] = False,
pem: typing.Optional[bool] = False,
+ full_chain: typing.Optional[bool] = False,
fingerprint: typing.Optional[ArgsFingerprint] = None,
):
headers = [
@@ -1284,7 +1309,10 @@ def show_certificate(
continue
if name and pem and not (private or fingerprint):
- print(encode_certificate(cert))
+ if full_chain:
+ print(_encode_certificate_chain(cert, ca_certs))
+ else:
+ print(encode_certificate(cert))
return
elif name and fingerprint and not private:
print(get_certificate_fingerprint(cert, fingerprint))
@@ -1298,13 +1326,19 @@ def show_certificate(
wrap_tags=True,
)
if private_key:
- print(encode_private_key(private_key, passphrase=None))
+ priv_pem = encode_private_key(private_key, passphrase=None)
+ if pem and full_chain:
+ print(_encode_certificate_chain(cert, ca_certs) + priv_pem)
+ else:
+ print(priv_pem)
+ return
else:
if protected:
print(f'Private key for certificate "{cert_name}" is '
'password-protected and cannot be displayed')
else:
- print(f'Failed to load private key for certificate "{cert_name}"')
+ print('Failed to load private key for certificate '
+ f'"{cert_name}"')
else:
print(f'No private key found for certificate "{cert_name}"')
return
@@ -1394,6 +1428,7 @@ def show_all(raw: bool):
print('\n')
show_crl(raw)
+
def renew_certbot(raw: bool, force: typing.Optional[bool] = False):
from vyos.defaults import directories
@@ -1406,7 +1441,7 @@ def renew_certbot(raw: bool, force: typing.Optional[bool] = False):
# Re-run CLI PKI helper to initially request certificates via ACME
# again. This should never be the case - but sometimes the universe has
# a bad time
- Warning(f'Directory "{certbot_config}" missing. Reinitializing PKI ' \
+ Warning(f'Directory "{certbot_config}" missing. Reinitializing PKI '
'subsystem...\n\n')
out = cmd(f'sudo sg vyattacfg -c "{vyos_conf_scripts_dir}/pki.py"')
elif force:
@@ -1416,6 +1451,7 @@ def renew_certbot(raw: bool, force: typing.Optional[bool] = False):
print(out)
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])