Import Upstream version 4.12.4

This commit is contained in:
geos_one
2025-08-12 22:28:56 +02:00
parent 03a8170b15
commit 9181ee2487
1629 changed files with 874094 additions and 554378 deletions

View File

@@ -34,3 +34,11 @@ class automember_add_condition(MethodOverride):
flags=['suppress_empty'],
),
)
@register(override=True, no_fail=True)
class automember_rebuild(MethodOverride):
def interactive_prompt_callback(self, kw):
msg = _('IMPORTANT: In case of a high number of users, hosts or '
'groups, the operation may require high CPU usage.')
self.Backend.textui.print_plain(msg)

View File

@@ -104,13 +104,15 @@ class automountlocation_tofiles(MethodOverride):
textui.print_plain('/etc/%s:' % m['automountmapname'])
for k in orphankeys:
if len(k) == 0: continue
dn = DN(k[0]['dn'])
if dn['automountmapname'] == m['automountmapname'][0]:
textui.print_plain(
'%s\t%s' % (
k[0]['automountkey'][0], k[0]['automountinformation'][0]
for key in k:
dn = DN(key['dn'])
if dn['automountmapname'] == m['automountmapname'][0]:
textui.print_plain(
'%s\t%s' % (
key['automountkey'][0],
key['automountinformation'][0]
)
)
)
@register()
@@ -216,8 +218,8 @@ class automountlocation_import(Command):
# Now iterate over the map files and add the keys. To handle
# continuation lines I'll make a pass through it to skip comments
# etc and also to combine lines.
for m in maps:
map = self.__read_mapfile(maps[m])
for m, filename in maps.items():
map = self.__read_mapfile(filename)
lines = []
cont = ''
for x in map:
@@ -225,7 +227,7 @@ class automountlocation_import(Command):
continue
x = x.rstrip()
if x.startswith('+'):
result['skipped'].append([m, maps[m]])
result['skipped'].append([m, filename])
continue
if len(x) == 0:
continue

View File

@@ -0,0 +1,107 @@
#
# Copyright (C) 2022 FreeIPA Contributors see COPYING for license
#
import os
import logging
import subprocess
from ipaclient.frontend import MethodOverride
from ipalib import errors
from ipalib import Bool, Flag, StrEnum
from ipalib.text import _
from ipaplatform.paths import paths
logger = logging.getLogger(__name__)
class baseuser_add_passkey(MethodOverride):
takes_options = (
Flag(
'register',
cli_name='register',
doc=_('Register the passkey'),
),
Bool(
'require_user_verification?',
cli_name='require_user_verification',
doc=_('Require user verification during authentication with '
'the passkey')
),
StrEnum(
'cosetype?',
cli_name='cose_type',
doc=_('COSE type to use for registration'),
values=('es256', 'rs256', 'eddsa'),
),
StrEnum(
'credtype?',
cli_name="cred_type",
doc=_('Credential type'),
values=('server-side', 'discoverable'),
),
)
def get_args(self):
# ipapasskey is not mandatory as it can be built
# from the registration step
for arg in super(baseuser_add_passkey, self).get_args():
if arg.name == 'ipapasskey':
yield arg.clone(required=False, alwaysask=False)
else:
yield arg.clone()
def forward(self, *args, **options):
if self.api.env.context == 'cli':
# 2 formats are possible for ipa user-add-passkey:
# --register [--require-user-verification] [--cose-type ...]
# or
# passkey:<key id>,<pub key>
for option in super(baseuser_add_passkey, self).get_options():
if args and option in options:
raise errors.MutuallyExclusiveError(
reason=_("cannot specify both %s and "
"passkey mapping").format(option))
# if the first format is used, need to register the key first
# and obtained the data
if 'register' in options:
# Ensure the executable exists
if not os.path.exists(paths.PASSKEY_CHILD):
raise errors.ValidationError(name="register", error=_(
"Missing executable %s, use the command with "
"LOGIN PASSKEY instead of LOGIN --register")
% paths.PASSKEY_CHILD)
options.pop('register')
cosetype = options.pop('cosetype', None)
require_verif = options.pop('require_user_verification', None)
credtype = options.pop('credtype', None)
cmd = [paths.PASSKEY_CHILD, "--register",
"--domain", self.api.env.domain,
"--username", args[0]]
if cosetype:
cmd.append("--type")
cmd.append(cosetype)
if require_verif is not None:
cmd.append("--user-verification")
cmd.append(str(require_verif).lower())
if credtype:
cmd.append("--cred-type")
cmd.append(credtype)
logger.debug("Executing command: %s", cmd)
passkey = None
with subprocess.Popen(cmd, stdout=subprocess.PIPE,
bufsize=1,
universal_newlines=True) as subp:
for line in subp.stdout:
if line.startswith("passkey:"):
passkey = line.strip()
else:
print(line.strip())
if subp.returncode != 0:
raise errors.NotFound(reason="Failed to generate passkey")
args = (args[0], [passkey])
return super(baseuser_add_passkey, self).forward(*args, **options)

View File

@@ -26,15 +26,16 @@ class WithCertOutArgs(MethodOverride):
filename = None
if 'certificate_out' in options:
filename = options.pop('certificate_out')
result = super(WithCertOutArgs, self).forward(*keys, **options)
if filename:
try:
util.check_writable_file(filename)
except errors.FileError as e:
raise errors.ValidationError(name='certificate-out',
error=str(e))
result = super(WithCertOutArgs, self).forward(*keys, **options)
if filename:
# if result certificate / certificate_chain not present in result,
# it means Dogtag did not provide it (probably due to LWCA key
# replication lag or failure. The server transmits a warning

View File

@@ -21,8 +21,6 @@
import base64
import six
from ipaclient.frontend import MethodOverride
from ipalib import errors
from ipalib import x509
@@ -31,9 +29,6 @@ from ipalib.parameters import BinaryFile, File, Flag, Str
from ipalib.plugable import Registry
from ipalib.text import _
if six.PY3:
unicode = str
register = Registry()
@@ -48,112 +43,36 @@ class CertRetrieveOverride(MethodOverride):
)
def forward(self, *args, **options):
filename = None
if 'certificate_out' in options:
certificate_out = options.pop('certificate_out')
try:
util.check_writable_file(certificate_out)
except errors.FileError as e:
raise errors.ValidationError(name='certificate-out',
error=str(e))
else:
certificate_out = None
filename = options.pop('certificate_out')
result = super(CertRetrieveOverride, self).forward(*args, **options)
if certificate_out is not None:
if filename is not None:
try:
util.check_writable_file(filename)
except errors.FileError as e:
raise errors.ValidationError(name='certificate-out',
error=str(e))
if options.get('chain', False):
certs = result['result']['certificate_chain']
else:
certs = [base64.b64decode(result['result']['certificate'])]
certs = (x509.load_der_x509_certificate(cert) for cert in certs)
x509.write_certificate_list(certs, certificate_out)
x509.write_certificate_list(certs, filename)
return result
@register(override=True, no_fail=True)
class cert_request(CertRetrieveOverride):
takes_options = CertRetrieveOverride.takes_options + (
Str(
'database?',
label=_('Path to NSS database'),
doc=_('Path to NSS database to use for private key'),
),
Str(
'private_key?',
label=_('Path to private key file'),
doc=_('Path to PEM file containing a private key'),
),
Str(
'password_file?',
label=_(
'File containing a password for the private key or database'),
),
Str(
'csr_profile_id?',
label=_('Name of CSR generation profile (if not the same as'
' profile_id)'),
),
)
def get_args(self):
for arg in super(cert_request, self).get_args():
if arg.name == 'csr':
arg = arg.clone_retype(arg.name, File, required=False)
yield arg
def forward(self, csr=None, **options):
database = options.pop('database', None)
private_key = options.pop('private_key', None)
csr_profile_id = options.pop('csr_profile_id', None)
password_file = options.pop('password_file', None)
if csr is None:
# Deferred import, ipaclient.csrgen is expensive to load.
# see https://pagure.io/freeipa/issue/7484
from ipaclient import csrgen
if database:
adaptor = csrgen.NSSAdaptor(database, password_file)
elif private_key:
adaptor = csrgen.OpenSSLAdaptor(
key_filename=private_key, password_filename=password_file)
else:
raise errors.InvocationError(
message=u"One of 'database' or 'private_key' is required")
pubkey_info = adaptor.get_subject_public_key_info()
pubkey_info_b64 = base64.b64encode(pubkey_info)
# If csr_profile_id is passed, that takes precedence.
# Otherwise, use profile_id. If neither are passed, the default
# in cert_get_requestdata will be used.
profile_id = csr_profile_id
if profile_id is None:
profile_id = options.get('profile_id')
response = self.api.Command.cert_get_requestdata(
profile_id=profile_id,
principal=options.get('principal'),
public_key_info=pubkey_info_b64)
req_info_b64 = response['result']['request_info']
req_info = base64.b64decode(req_info_b64)
csr = adaptor.sign_csr(req_info)
if not csr:
raise errors.CertificateOperationError(
error=(_('Generated CSR was empty')))
else:
if database is not None or private_key is not None:
raise errors.MutuallyExclusiveError(reason=_(
"Options 'database' and 'private_key' are not compatible"
" with 'csr'"))
return super(cert_request, self).forward(csr, **options)
@register(override=True, no_fail=True)
class cert_show(CertRetrieveOverride):

View File

@@ -1,128 +0,0 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
import base64
import six
from ipalib import api
from ipalib import errors
from ipalib import output
from ipalib import util
from ipalib.frontend import Local, Str
from ipalib.parameters import Bytes, Principal
from ipalib.plugable import Registry
from ipalib.text import _
from ipapython import dogtag
if six.PY3:
unicode = str
register = Registry()
__doc__ = _("""
Commands to build certificate requests automatically
""")
@register()
class cert_get_requestdata(Local):
__doc__ = _('Gather data for a certificate signing request.')
NO_CLI = True
takes_options = (
Principal(
'principal',
label=_('Principal'),
doc=_('Principal for this certificate (e.g.'
' HTTP/test.example.com)'),
),
Str(
'profile_id?',
label=_('Profile ID'),
doc=_('CSR Generation Profile to use'),
),
Bytes(
'public_key_info',
label=_('Subject Public Key Info'),
doc=_('DER-encoded SubjectPublicKeyInfo structure'),
),
Str(
'out?',
doc=_('Write CertificationRequestInfo to file'),
),
)
has_output = (
output.Output(
'result',
type=dict,
doc=_('Dictionary mapping variable name to value'),
),
)
has_output_params = (
Str(
'request_info',
label=_('CertificationRequestInfo structure'),
)
)
def execute(self, *args, **options):
# Deferred import, ipaclient.csrgen is expensive to load.
# see https://pagure.io/freeipa/issue/7484
from ipaclient import csrgen
from ipaclient import csrgen_ffi
if 'out' in options:
util.check_writable_file(options['out'])
principal = options.get('principal')
profile_id = options.get('profile_id')
if profile_id is None:
profile_id = dogtag.DEFAULT_PROFILE
public_key_info = options.get('public_key_info')
public_key_info = base64.b64decode(public_key_info)
if self.api.env.in_server:
backend = self.api.Backend.ldap2
else:
backend = self.api.Backend.rpcclient
if not backend.isconnected():
backend.connect()
try:
if principal.is_host:
principal_obj = api.Command.host_show(
principal.hostname, all=True)
elif principal.is_service:
principal_obj = api.Command.service_show(
unicode(principal), all=True)
elif principal.is_user:
principal_obj = api.Command.user_show(
principal.username, all=True)
except errors.NotFound:
raise errors.NotFound(
reason=_("The principal for this request doesn't exist."))
principal_obj = principal_obj['result']
config = api.Command.config_show()['result']
generator = csrgen.CSRGenerator(csrgen.FileRuleProvider())
csr_config = generator.csr_config(principal_obj, config, profile_id)
request_info = base64.b64encode(csrgen_ffi.build_requestinfo(
csr_config.encode('utf8'), public_key_info))
result = {}
if 'out' in options:
with open(options['out'], 'wb') as f:
f.write(request_info)
else:
result = dict(request_info=request_info)
return dict(
result=result
)

View File

@@ -38,6 +38,8 @@ class hbactest(CommandOverride):
# Note that we don't actually use --detail below to see if details need
# to be printed as our execute() method will return None for corresponding
# entries and None entries will be skipped.
self.log_messages(output)
for o in self.output:
if o == 'value':
continue

View File

@@ -22,6 +22,7 @@ import sys
from ipaclient.frontend import MethodOverride
from ipalib import api, Str, Password, _
from ipalib import errors
from ipalib.messages import add_message, ResultFormattingError
from ipalib.plugable import Registry
from ipalib.frontend import Local
@@ -129,7 +130,6 @@ class HTTPSHandler(urllib.request.HTTPSHandler):
return create_https_connection(host, **tmp)
def https_open(self, req):
# pylint: disable=no-member
return self.do_open(self.__inner, req)
@register()
@@ -156,8 +156,6 @@ class otptoken_sync(Local):
segments = list(urllib.parse.urlparse(self.api.env.xmlrpc_uri))
assert segments[0] == 'https' # Ensure encryption.
segments[2] = segments[2].replace('/xml', '/session/sync_token')
# urlunparse *can* take one argument
# pylint: disable=too-many-function-args
sync_uri = urllib.parse.urlunparse(segments)
# Prepare the query.
@@ -170,7 +168,6 @@ class otptoken_sync(Local):
query = query.encode('utf-8')
# Sync the token.
# pylint: disable=E1101
handler = HTTPSHandler(
cafile=api.env.tls_ca_cert,
tls_version_min=api.env.tls_version_min,
@@ -180,11 +177,13 @@ class otptoken_sync(Local):
status['result'][self.header] = rsp.info().get(self.header, 'unknown')
rsp.close()
if status['result'][self.header] != "ok":
msg = {'error': 'Error contacting server!',
'invalid-credentials': 'Invalid Credentials!',
}.get(status['result'][self.header], 'Unknown Error!')
raise errors.ExecutionError(
message=_("Unable to synchronize token: %s") % msg)
return status
def output_for_cli(self, textui, result, *keys, **options):
textui.print_plain({
'ok': 'Token synchronized.',
'error': 'Error contacting server!',
'invalid-credentials': 'Invalid Credentials!',
}.get(result['result'][self.header], 'Unknown Error!'))
textui.print_plain('Token synchronized.')

View File

@@ -0,0 +1,14 @@
#
# Copyright (C) 2022 FreeIPA Contributors see COPYING for license
#
from ipaclient.plugins.baseuser import baseuser_add_passkey
from ipalib.plugable import Registry
from ipalib import _
register = Registry()
@register(override=True, no_fail=True)
class stageuser_add_passkey(baseuser_add_passkey):
__doc__ = _("Add one or more passkey mappings to the user entry.")

View File

@@ -39,9 +39,11 @@ class sudorule_disable(MethodOverride):
@register(override=True, no_fail=True)
class sudorule_add_option(MethodOverride):
def output_for_cli(self, textui, result, cn, **options):
opts = self.normalize(**options)
textui.print_dashed(
_('Added option "%(option)s" to Sudo Rule "%(rule)s"')
% dict(option=options['ipasudoopt'], rule=cn))
% dict(option=','.join(opts['ipasudoopt']), rule=cn)
)
super(sudorule_add_option, self).output_for_cli(textui, result, cn,
**options)
@@ -50,8 +52,10 @@ class sudorule_add_option(MethodOverride):
@register(override=True, no_fail=True)
class sudorule_remove_option(MethodOverride):
def output_for_cli(self, textui, result, cn, **options):
opts = self.normalize(**options)
textui.print_dashed(
_('Removed option "%(option)s" from Sudo Rule "%(rule)s"')
% dict(option=options['ipasudoopt'], rule=cn))
% dict(option=','.join(opts['ipasudoopt']), rule=cn)
)
super(sudorule_remove_option, self).output_for_cli(textui, result, cn,
**options)

View File

@@ -19,6 +19,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ipaclient.frontend import MethodOverride
from ipaclient.plugins.baseuser import baseuser_add_passkey
from ipalib import errors
from ipalib import Flag
from ipalib import util
@@ -79,3 +80,8 @@ class user_show(MethodOverride):
raise errors.NoCertificateError(entry=keys[-1])
else:
return super(user_show, self).forward(*keys, **options)
@register(override=True, no_fail=True)
class user_add_passkey(baseuser_add_passkey):
__doc__ = _("Add one or more passkey mappings to the user entry.")

View File

@@ -25,11 +25,12 @@ import io
import json
import logging
import os
import ssl
import tempfile
from cryptography.fernet import Fernet, InvalidToken
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
@@ -39,7 +40,7 @@ from cryptography.hazmat.primitives.serialization import (
from ipaclient.frontend import MethodOverride
from ipalib import x509
from ipalib.constants import USER_CACHE_PATH
from ipalib import constants
from ipalib.frontend import Local, Method, Object
from ipalib.util import classproperty
from ipalib import api, errors
@@ -118,8 +119,8 @@ def encrypt(data, symmetric_key=None, public_key=None):
return public_key_obj.encrypt(
data,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
@@ -153,8 +154,8 @@ def decrypt(data, symmetric_key=None, private_key=None):
return private_key_obj.decrypt(
data,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
@@ -546,41 +547,47 @@ class vault_mod(Local):
return response
class _TransportCertCache:
def __init__(self):
self._dirname = os.path.join(
USER_CACHE_PATH, 'ipa', 'kra-transport-certs'
)
class _KraConfigCache:
"""The KRA config cache stores vaultconfig-show result.
"""
def __init__(self, api):
self._dirname = os.path.join(api.env.cache_dir, 'kra-config')
def _get_filename(self, domain):
basename = DNSName(domain).ToASCII() + '.pem'
basename = DNSName(domain).ToASCII() + '.json'
return os.path.join(self._dirname, basename)
def load_cert(self, domain):
"""Load cert from cache
def load(self, domain):
"""Load config from cache
:param domain: IPA domain
:return: cryptography.x509.Certificate or None
:return: dict or None
"""
filename = self._get_filename(domain)
try:
try:
return x509.load_certificate_from_file(filename)
except EnvironmentError as e:
with open(filename) as f:
return json.load(f)
except OSError as e:
if e.errno != errno.ENOENT:
raise
except Exception:
logger.warning("Failed to load %s", filename, exc_info=True)
return None
def store_cert(self, domain, transport_cert):
"""Store a new cert or override existing cert
def store(self, domain, response):
"""Store config in cache
:param domain: IPA domain
:param transport_cert: cryptography.x509.Certificate
:return: True if cert was stored successfully
:param config: ipa vaultconfig-show response
:return: True if config was stored successfully
"""
config = response['result'].copy()
# store certificate as PEM-encoded ASCII
config['transport_cert'] = ssl.DER_cert_to_PEM_cert(
config['transport_cert']
)
filename = self._get_filename(domain)
pem = transport_cert.public_bytes(serialization.Encoding.PEM)
try:
try:
os.makedirs(self._dirname)
@@ -588,9 +595,9 @@ class _TransportCertCache:
if e.errno != errno.EEXIST:
raise
with tempfile.NamedTemporaryFile(dir=self._dirname, delete=False,
mode='wb') as f:
mode='w') as f:
try:
f.write(pem)
json.dump(config, f)
ipautil.flush_sync(f)
f.close()
os.rename(f.name, filename)
@@ -603,8 +610,8 @@ class _TransportCertCache:
else:
return True
def remove_cert(self, domain):
"""Remove a cert from cache, ignores errors
def remove(self, domain):
"""Remove a config from cache, ignores errors
:param domain: IPA domain
:return: True if cert was found and removed
@@ -620,7 +627,7 @@ class _TransportCertCache:
return True
_transport_cert_cache = _TransportCertCache()
_kra_config_cache = _KraConfigCache(api)
@register(override=True, no_fail=True)
@@ -635,13 +642,8 @@ class vaultconfig_show(MethodOverride):
response = super(vaultconfig_show, self).forward(*args, **options)
# cache transport certificate
transport_cert = x509.load_der_x509_certificate(
response['result']['transport_cert'])
_transport_cert_cache.store_cert(
self.api.env.domain, transport_cert
)
# cache config
_kra_config_cache.store(self.api.env.domain, response)
if file:
with open(file, 'wb') as f:
@@ -651,20 +653,89 @@ class vaultconfig_show(MethodOverride):
class ModVaultData(Local):
def _generate_session_key(self):
key_length = max(algorithms.TripleDES.key_sizes)
algo = algorithms.TripleDES(os.urandom(key_length // 8))
return algo
def _generate_session_key(self, name):
if name not in constants.VAULT_WRAPPING_SUPPORTED_ALGOS:
msg = _("{algo} is not a supported vault wrapping algorithm")
raise errors.ValidationError(msg.format(algo=repr(name)))
if name == constants.VAULT_WRAPPING_AES128_CBC:
return algorithms.AES(os.urandom(128 // 8))
elif name == constants.VAULT_WRAPPING_3DES:
return algorithms.TripleDES(os.urandom(196 // 8))
else:
# unreachable
raise ValueError(name)
def _get_vaultconfig(self, force_refresh=False):
config = None
if not force_refresh:
config = _kra_config_cache.load(self.api.env.domain)
if config is None:
# vaultconfig_show also caches data
response = self.api.Command.vaultconfig_show()
config = response['result']
transport_cert = x509.load_der_x509_certificate(
config['transport_cert']
)
else:
# cached JSON uses PEM-encoded ASCII string
transport_cert = x509.load_pem_x509_certificate(
config['transport_cert'].encode('ascii')
)
default_algo = config.get('wrapping_default_algorithm')
if default_algo is None:
# old server
wrapping_algo = constants.VAULT_WRAPPING_3DES
elif default_algo in constants.VAULT_WRAPPING_SUPPORTED_ALGOS:
# try to use server default
wrapping_algo = default_algo
else:
# prefer server's sorting order
for algo in config['wrapping_supported_algorithms']:
if algo in constants.VAULT_WRAPPING_SUPPORTED_ALGOS:
wrapping_algo = algo
break
else:
raise errors.ValidationError(
"No overlapping wrapping algorithm between server and "
"client."
)
return transport_cert, wrapping_algo
def _do_internal(self, algo, transport_cert, raise_unexpected,
*args, **options):
use_oaep=False, *args, **options):
public_key = transport_cert.public_key()
# wrap session key with transport certificate
wrapped_session_key = public_key.encrypt(
algo.key,
padding.PKCS1v15()
)
# KRA may be configured using either the default PKCS1v15 or RSA-OAEP.
# there is no way to query this info using the REST interface.
if not use_oaep:
# PKCS1v15() causes an OpenSSL exception when FIPS is enabled
# if so, we fallback to RSA-OAEP
try:
wrapped_session_key = public_key.encrypt(
algo.key,
padding.PKCS1v15()
)
except ValueError:
wrapped_session_key = public_key.encrypt(
algo.key,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
else:
wrapped_session_key = public_key.encrypt(
algo.key,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
options['session_key'] = wrapped_session_key
name = self.name + '_internal'
@@ -674,31 +745,38 @@ class ModVaultData(Local):
except (errors.InternalError,
errors.ExecutionError,
errors.GenericError):
_transport_cert_cache.remove_cert(self.api.env.domain)
if raise_unexpected:
_kra_config_cache.remove(self.api.env.domain)
if raise_unexpected and use_oaep:
raise
return None
def internal(self, algo, *args, **options):
def internal(self, algo, transport_cert, *args, **options):
"""
Calls the internal counterpart of the command.
"""
domain = self.api.env.domain
# try call with cached transport certificate
transport_cert = _transport_cert_cache.load_cert(domain)
if transport_cert is not None:
try:
result = self._do_internal(algo, transport_cert, False,
*args, **options)
if result is not None:
return result
False, *args, **options)
except errors.EncodingError:
result = self._do_internal(algo, transport_cert, False,
True, *args, **options)
if result is not None:
return result
# retrieve transport certificate (cached by vaultconfig_show)
response = self.api.Command.vaultconfig_show()
transport_cert = x509.load_der_x509_certificate(
response['result']['transport_cert'])
transport_cert = self._get_vaultconfig(force_refresh=True)[0]
# call with the retrieved transport certificate
result = self._do_internal(algo, transport_cert, True,
False, *args, **options)
if result is not None:
return result
# call and use_oaep this time, last attempt
return self._do_internal(algo, transport_cert, True,
*args, **options)
True, *args, **options)
@register(no_fail=True)
@@ -758,7 +836,8 @@ class vault_archive(ModVaultData):
if option.name not in ('nonce',
'session_key',
'vault_data',
'version'):
'version',
'wrapping_algo'):
yield option
for option in super(vault_archive, self).get_options():
yield option
@@ -775,7 +854,7 @@ class vault_archive(ModVaultData):
def _wrap_data(self, algo, json_vault_data):
"""Encrypt data with wrapped session key and transport cert
:param bytes algo: wrapping algorithm instance
:param algo: wrapping algorithm instance
:param bytes json_vault_data: dumped vault data
:return:
"""
@@ -927,15 +1006,24 @@ class vault_archive(ModVaultData):
json_vault_data = json.dumps(vault_data).encode('utf-8')
# get config
transport_cert, wrapping_algo = self._get_vaultconfig()
# let options override wrapping algo
# For backwards compatibility do not send old legacy wrapping algo
# to server. Only send the option when non-3DES is used.
wrapping_algo = options.pop('wrapping_algo', wrapping_algo)
if wrapping_algo != constants.VAULT_WRAPPING_3DES:
options['wrapping_algo'] = wrapping_algo
# generate session key
algo = self._generate_session_key()
algo = self._generate_session_key(wrapping_algo)
# wrap vault data
nonce, wrapped_vault_data = self._wrap_data(algo, json_vault_data)
options.update(
nonce=nonce,
vault_data=wrapped_vault_data
)
return self.internal(algo, *args, **options)
return self.internal(algo, transport_cert, *args, **options)
@register(no_fail=True)
@@ -1001,7 +1089,7 @@ class vault_retrieve(ModVaultData):
def get_options(self):
for option in self.api.Command.vault_retrieve_internal.options():
if option.name not in ('session_key', 'version'):
if option.name not in ('session_key', 'version', 'wrapping_algo'):
yield option
for option in super(vault_retrieve, self).get_options():
yield option
@@ -1059,10 +1147,19 @@ class vault_retrieve(ModVaultData):
vault = self.api.Command.vault_show(*args, **options)['result']
vault_type = vault['ipavaulttype'][0]
# get config
transport_cert, wrapping_algo = self._get_vaultconfig()
# let options override wrapping algo
# For backwards compatibility do not send old legacy wrapping algo
# to server. Only send the option when non-3DES is used.
wrapping_algo = options.pop('wrapping_algo', wrapping_algo)
if wrapping_algo != constants.VAULT_WRAPPING_3DES:
options['wrapping_algo'] = wrapping_algo
# generate session key
algo = self._generate_session_key()
algo = self._generate_session_key(wrapping_algo)
# send retrieval request to server
response = self.internal(algo, *args, **options)
response = self.internal(algo, transport_cert, *args, **options)
# unwrap data with session key
vault_data = self._unwrap_response(
algo,