diff options
author | Christian Breunig <christian@breunig.cc> | 2024-02-11 21:49:25 +0100 |
---|---|---|
committer | Mergify <37929162+mergify[bot]@users.noreply.github.com> | 2024-02-13 20:39:59 +0000 |
commit | 71397fb4070758e5ce29be08a3572bf3ee62cb06 (patch) | |
tree | b158fcc148d051e21ba04e328f0a7632cabf0bbb | |
parent | 74a86612a207bb84412840a2ac4ff24975c1b8e7 (diff) | |
download | vyos-1x-71397fb4070758e5ce29be08a3572bf3ee62cb06.tar.gz vyos-1x-71397fb4070758e5ce29be08a3572bf3ee62cb06.zip |
pki: T6034: add OpenSSH key support
set pki openssh rpki private key ...
set pki openssh rpki public key ...
set pki openssh rpki public type 'ssh-rsa'
(cherry picked from commit 8c78ef0879f22ffd4a5f7fdb175e9109b46e9d7b)
-rw-r--r-- | interface-definitions/include/pki/openssh-key.xml.i | 14 | ||||
-rw-r--r-- | interface-definitions/pki.xml.in | 39 | ||||
-rw-r--r-- | python/vyos/pki.py | 31 | ||||
-rwxr-xr-x | src/conf_mode/pki.py | 32 |
4 files changed, 112 insertions, 4 deletions
diff --git a/interface-definitions/include/pki/openssh-key.xml.i b/interface-definitions/include/pki/openssh-key.xml.i new file mode 100644 index 000000000..8f005d077 --- /dev/null +++ b/interface-definitions/include/pki/openssh-key.xml.i @@ -0,0 +1,14 @@ +<!-- include start from pki/openssh-key.xml.i --> +<leafNode name="key"> + <properties> + <help>OpenSSH key in PKI configuration</help> + <completionHelp> + <path>pki openssh</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of OpenSSH key in PKI configuration</description> + </valueHelp> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/pki.xml.in b/interface-definitions/pki.xml.in index 617bdd584..7a0b073b4 100644 --- a/interface-definitions/pki.xml.in +++ b/interface-definitions/pki.xml.in @@ -168,6 +168,45 @@ </properties> <children> #include <include/pki/cli-public-key-base64.xml.i> + <leafNode name="type"> + <properties> + <help>SSH public key type</help> + <completionHelp> + <list>ssh-rsa</list> + </completionHelp> + <valueHelp> + <format>ssh-rsa</format> + <description>Key pair based on RSA algorithm</description> + </valueHelp> + <constraint> + <regex>(ssh-rsa)</regex> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="private"> + <properties> + <help>Private key</help> + </properties> + <children> + #include <include/pki/cli-private-key-base64.xml.i> + #include <include/pki/password-protected.xml.i> + </children> + </node> + </children> + </tagNode> + <tagNode name="openssh"> + <properties> + <help>OpenSSH public and private keys</help> + </properties> + <children> + <node name="public"> + <properties> + <help>Public key</help> + </properties> + <children> + #include <include/pki/cli-public-key-base64.xml.i> </children> </node> <node name="private"> diff --git a/python/vyos/pki.py b/python/vyos/pki.py index 792e24b76..02dece471 100644 --- a/python/vyos/pki.py +++ b/python/vyos/pki.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2023 VyOS maintainers and contributors +# Copyright (C) 2023-2024 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -20,7 +20,9 @@ import ipaddress from cryptography import x509 from cryptography.exceptions import InvalidSignature from cryptography.x509.extensions import ExtensionNotFound -from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID, ExtensionOID +from cryptography.x509.oid import NameOID +from cryptography.x509.oid import ExtendedKeyUsageOID +from cryptography.x509.oid import ExtensionOID from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import dh @@ -45,6 +47,8 @@ DH_BEGIN='-----BEGIN DH PARAMETERS-----\n' DH_END='\n-----END DH PARAMETERS-----' OVPN_BEGIN = '-----BEGIN OpenVPN Static key V{0}-----\n' OVPN_END = '\n-----END OpenVPN Static key V{0}-----' +OPENSSH_KEY_BEGIN='-----BEGIN OPENSSH PRIVATE KEY-----\n' +OPENSSH_KEY_END='\n-----END OPENSSH PRIVATE KEY-----' # Print functions @@ -229,6 +233,12 @@ def wrap_public_key(raw_data): def wrap_private_key(raw_data, passphrase=None): return (KEY_ENC_BEGIN if passphrase else KEY_BEGIN) + raw_data + (KEY_ENC_END if passphrase else KEY_END) +def wrap_openssh_public_key(raw_data, type): + return f'{type} {raw_data}' + +def wrap_openssh_private_key(raw_data): + return OPENSSH_KEY_BEGIN + raw_data + OPENSSH_KEY_END + def wrap_certificate_request(raw_data): return CSR_BEGIN + raw_data + CSR_END @@ -245,7 +255,6 @@ def wrap_openvpn_key(raw_data, version='1'): return OVPN_BEGIN.format(version) + raw_data + OVPN_END.format(version) # Load functions - def load_public_key(raw_data, wrap_tags=True): if wrap_tags: raw_data = wrap_public_key(raw_data) @@ -267,6 +276,21 @@ def load_private_key(raw_data, passphrase=None, wrap_tags=True): except ValueError: return False +def load_openssh_public_key(raw_data, type): + try: + return serialization.load_ssh_public_key(bytes(f'{type} {raw_data}', 'utf-8')) + except ValueError: + return False + +def load_openssh_private_key(raw_data, passphrase=None, wrap_tags=True): + if wrap_tags: + raw_data = wrap_openssh_private_key(raw_data) + + try: + return serialization.load_ssh_private_key(bytes(raw_data, 'utf-8'), password=passphrase) + except ValueError: + return False + def load_certificate_request(raw_data, wrap_tags=True): if wrap_tags: raw_data = wrap_certificate_request(raw_data) @@ -429,4 +453,3 @@ def sort_ca_chain(ca_names, pki_node): 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/src/conf_mode/pki.py b/src/conf_mode/pki.py index 4be40e99e..2d076e42d 100755 --- a/src/conf_mode/pki.py +++ b/src/conf_mode/pki.py @@ -29,6 +29,8 @@ from vyos.defaults import directories from vyos.pki import is_ca_certificate from vyos.pki import load_certificate from vyos.pki import load_public_key +from vyos.pki import load_openssh_public_key +from vyos.pki import load_openssh_private_key from vyos.pki import load_private_key from vyos.pki import load_crl from vyos.pki import load_dh_parameters @@ -150,6 +152,11 @@ def get_config(config=None): if 'changed' not in pki: pki.update({'changed':{}}) pki['changed'].update({'key_pair' : tmp}) + tmp = node_changed(conf, base + ['openssh'], recursive=True) + if tmp: + if 'changed' not in pki: pki.update({'changed':{}}) + pki['changed'].update({'openssh' : tmp}) + tmp = node_changed(conf, base + ['openvpn', 'shared-secret'], recursive=True) if tmp: if 'changed' not in pki: pki.update({'changed':{}}) @@ -241,6 +248,17 @@ def is_valid_private_key(raw_data, protected=False): return True return load_private_key(raw_data, passphrase=None, wrap_tags=True) +def is_valid_openssh_public_key(raw_data, type): + # If it loads correctly we're good, or return False + return load_openssh_public_key(raw_data, type) + +def is_valid_openssh_private_key(raw_data, protected=False): + # If it loads correctly we're good, or return False + # With encrypted private keys, we always return true as we cannot ask for password to verify + if protected: + return True + return load_openssh_private_key(raw_data, passphrase=None, wrap_tags=True) + def is_valid_crl(raw_data): # If it loads correctly we're good, or return False return load_crl(raw_data, wrap_tags=True) @@ -322,6 +340,20 @@ def verify(pki): if not is_valid_private_key(private['key'], protected): raise ConfigError(f'Invalid private key on key-pair "{name}"') + if 'openssh' in pki: + for name, key_conf in pki['openssh'].items(): + if 'public' in key_conf and 'key' in key_conf['public']: + if 'type' not in key_conf['public']: + raise ConfigError(f'Must define OpenSSH public key type for "{name}"') + if not is_valid_openssh_public_key(key_conf['public']['key'], key_conf['public']['type']): + raise ConfigError(f'Invalid OpenSSH public key "{name}"') + + if 'private' in key_conf and 'key' in key_conf['private']: + private = key_conf['private'] + protected = 'password_protected' in private + if not is_valid_openssh_private_key(private['key'], protected): + raise ConfigError(f'Invalid OpenSSH private key "{name}"') + if 'x509' in pki: if 'default' in pki['x509']: default_values = pki['x509']['default'] |