Please take a look at the attached patch to add vault-archive/retrieve commands.

On 4/20/2015 1:12 AM, Jan Cholasta wrote:
16) You do way too much stuff in vault_add.forward(). Only code that
must be done on the client needs to be there, i.e. handling of the
"data", "text" and "in" options.

The vault_archive call must be in vault_add.execute(), otherwise a) we
will be making 2 RPC calls from the client and b) it won't be
called at
all when api.env.in_server is True.

This is done by design. The vault_add.forward() generates the salt and
the keys. The vault_archive.forward() will encrypt the data. These
operations have to be done on the client side to secure the
transport of
the data from the client through the server and finally to KRA. This
mechanism prevents the server from looking at the unencrypted data.

OK, but that does not justify that it's broken in server-side API. It
can and should be done so that it works the same way on both client and
server.

I think the best solution would be to split the command into two
commands, server-side vault_archive_raw to archive already encrypted
data, and client-side vault_archive to encrypt data and archive them
with vault_archive_raw in its .execute(). Same thing for vault_retrieve.

Actually I think it's better to just merge the add and archive, reducing
the number of RPC calls. The vault_archive now will support two types of
operations:

(a) Archive data into a new vault (it will create the vault just before
archiving the data):

   $ ipa vault-archive testvault --create --in data ...

(b) Archive data into an existing vault:

   $ ipa vault-archive testvault --in data ...

The vault_add is now just a wrapper for the vault_archive(a).

If that's just an implementation detail, OK.

If it's possible to modify existing vault objects using vault-add or
create new objects using vault-archive, then NACK.

The vault-archive is now completely separate from vault-add. You can no longer archive data while creating a vault:

  $ ipa vault-add test
  $ ipa vault-archive test --in secret.bin

BTW, I also think it would be better if there were 2 separate sets of
commands for binary and textual data
(vault_{archive,retrieve}_{data,text}) rather than trying to handle
everything in vault_{archive,retrieve}.

I don't think we want to provide a separate command of every possible
data type & operation combination. Users would get confused. The archive
& retrieve commands should be able to handle all current & future data
types with options.

A command with two sets of mutually exclusive options is really two
commands in disguise, which is a good sign it should be divided into two
actual commands.

Who are you to say users would get confused? I say they would be more
confused by a command with a plethora of mutually exclusive "options".

What other possible data types are there?

The patch has been simplified to support only binary data. The data can be specified using either of these options:

  $ ipa vault-archive test --data <base-64 encoded data>
  $ ipa vault-archive test --in <input file>

I don't think we want to provide two separate commands for these options although they are mutually exclusive, do we?

The add & archive combination was added for convenience, not for
optimization. This way you would be able to archive data into a new
vault using a single command. Without this, you'd have to execute two
separate commands: add & archive, which will result in 2 RPC calls
anyway.

I think I would prefer if it was separate, as that would be consistent
with other plugins (e.g. for objects with members, we don't allow adding
members directly in -add, you have to use -add-member after -add).

The vault data is similar to group description, not group members. When
creating a group we can supply the description. If not specified it will
be blank. Archiving vault data is similar to updating the group
description.

It's similar to group members because there are separate commands to
manipulate them.

Just because there are separate commands doesn't mean vault data (single-valued) is similar to group members (multi-valued). It uses separate commands because of the encryption steps involved while archiving/retrieving data.

You have to choose one of:

   a) vault data is settable using vault-add and vault-mod and gettable
using vault-mod, vault-show and vault-find

   b) vault data is settable using vault-archive and gettable using
vault-retrieve

Anything in between is not permitted.

As mentioned above, the add and archive commands are now separate.

21) vault_archive is not a retrieve operation, it should be based on
LDAPUpdate instead of LDAPRetrieve. Or Command actually, since it does
not do anything with LDAP. The same applies to vault_retrieve.

The vault_archive does not actually modify the LDAP entry because it
stores the data in KRA. It is actually an LDAPRetrieve operation
because
it needs to get the vault info before it can perform the archival
operation. Same thing with vault_retrieve.

It is not a LDAPRetrieve operation, because it has different semantics.
Please use Command as base class and either use ldap2 for direct LDAP or
call vault_show instead of hacking around LDAPRetrieve.

It's been changed to inherit from LDAPQuery instead.

NACK, it's not a LDAPQuery operation, because it has different
semantics. There is more to a command than executing code, so you should
use a correct base class.

Changed to inherit from Command as requested. Now these commands no longer have a direct access to the vault object (self.obj) although they are accessing vault objects like other vault commands. Also now the vault name argument has to be added explicitly on each command.

There are existing commands that inherit from LDAPQuery while doing other things too, so the 'semantic' seems to be kind of arbitrarily defined and subjective.

22) vault_archive will break with binary data that is not UTF-8
encoded
text.

This is where it occurs:

+        vault_data[u'data'] = unicode(data)

Generally, don't use unicode() on str values and str() on unicode
values
directly, always use .decode() and .encode().

The unicode(s, encoding) is actually equivalent to s.decode(encoding),
so the following code will not solve the problem:

   vault_data[u'data'] = data.decode()

As you said, decode() will only work if the data being decoded actually
follows the encoding rules (i.e. already UTF-8 encoded).

It needs to be a Unicode because json.dumps() doesn't work with binary
data. Fixed by adding base-64 encoding.

The base-64 encoding is necessary to convert random binaries into ASCII
so it can be decoded into Unicode. Here is the current code:

   vault_data[u'data'] = unicode(base64.b64encode(data))

which is equivalent to:

   vault_data[u'data'] = base64.b64encode(data).decode()

If you read a little bit further, you would get to the point, which is
certainly not calling .decode() without arguments, but *always
explicitly specify the encoding*.

Added the explicit encoding name although it's not necessary since the data being encoded/decoded is base-64 encoded (i.e. ASCII).

If something str needs to be unicode, you should use .decode() to
explicitly specify the encoding, instead of relying on unicode() to pick
the correct one.

Since we know this is ASCII data we can now specify UTF-8 encoding.

   vault_data[u'data'] = base64.b64encode(data).decode('utf-8')

But for anything that comes from user input (e.g. filenames, passwords),
it's better to use the default encoding because that can be configured
by the user.

You are confusing user's configured encoding with Python's default
encoding. Default encoding in Python isn't derived from user's
localization settings.

Anyway, anything that comes from user input is already decoded using
user's configured encoding when it enters the framework so I don't know
why are you even bringing it up here.

It's irrelevant now that the command only supports binary data.

Anyway, I think a better solution than base64 would be to use the
"raw_unicode_escape" encoding:

As explained above, base-64 encoding is necessary because random
binaries don't follow any encoding rules. I'd rather not use
raw_unicode_escape because it's not really a text data.

The result of decoding binary data using raw_unicode_escape is perfectly
valid unicode data which doesn't eat 33% more space like base64 encoded
binary does, hence my suggestion.

Anyway, it turns out when encoded in JSON, raw_unicode_escape string
generally takes more space than base64 encoded string because of JSON
escaping, so base64 is indeed better.

Unchanged. It still uses base-64 encoding.

Here's how it's
now implemented:

     if data:
         data = data.decode('raw_unicode_escape')

Input data is already in binaries, no conversion needed.

     elif text:
         data = text

Input text will be converted to binaries with default encoding:

   data = text.encode()

See what the default encoding actually is and why you shouldn't rely on
it above.

Irrelevant now.

     elif input_file:
         with open(input_file, 'rb') as f:
             data = f.read()
         data = data.decode('raw_unicode_escape')

Input contains binary data, no conversion needed.

     else:
         data = u''

If not specified, the data will be empty string:

   data = ''

The data needs to be converted into binaries so it can be encrypted
before transport (depending on the vault type):

   data = self.obj.encrypt(data, ...)

     vault_data[u'data'] = data

Then for transport the data is base-64 encoded first, then converted
into Unicode:

   vault_data[u'data'] = base64.b64encode(data).decode('utf-8')

--
Endi S. Dewata
>From bdd5c6900509e223ba54aa105a77f8404579e2b7 Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edew...@redhat.com>
Date: Tue, 21 Oct 2014 10:57:08 -0400
Subject: [PATCH] Added vault-archive and vault-retrieve commands.

New commands have been added to archive and retrieve
data into and from a vault, also to retrieve the
transport certificate.

https://fedorahosted.org/freeipa/ticket/3872
---
 API.txt                                   |  28 ++
 VERSION                                   |   4 +-
 ipalib/plugins/vault.py                   | 452 +++++++++++++++++++++++++++++-
 ipatests/test_xmlrpc/test_vault_plugin.py |  71 ++++-
 4 files changed, 551 insertions(+), 4 deletions(-)

diff --git a/API.txt b/API.txt
index 
da69f32de5c12c0d85a7d61d9027385aa3c0ee05..3741e6f16689e43838c2d31a44872d1ea47589c7
 100644
--- a/API.txt
+++ b/API.txt
@@ -4768,6 +4768,24 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an 
LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: vault_archive
+args: 1,9,1
+arg: Str('cn', cli_name='name', maxlength=255, pattern='^[a-zA-Z0-9_.-]+$')
+option: Bytes('data?')
+option: Str('in?')
+option: Str('nonce?')
+option: Str('service?')
+option: Str('session_key?')
+option: Flag('shared?', autofill=True, default=False)
+option: Str('user?')
+option: Str('vault_data?')
+option: Str('version?', exclude='webui')
+output: Output('result', None, None)
+command: vault_config
+args: 0,2,1
+option: Str('transport_out?')
+option: Str('version?', exclude='webui')
+output: Output('result', None, None)
 command: vault_del
 args: 1,5,3
 arg: Str('cn', attribute=True, cli_name='name', maxlength=255, 
multivalue=True, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, 
required=True)
@@ -4814,6 +4832,16 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an 
LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: vault_retrieve
+args: 1,6,1
+arg: Str('cn', cli_name='name', maxlength=255, pattern='^[a-zA-Z0-9_.-]+$')
+option: Str('out?')
+option: Str('service?')
+option: Str('session_key?')
+option: Flag('shared?', autofill=True, default=False)
+option: Str('user?')
+option: Str('version?', exclude='webui')
+output: Output('result', None, None)
 command: vault_show
 args: 1,7,3
 arg: Str('cn', attribute=True, cli_name='name', maxlength=255, 
multivalue=False, pattern='^[a-zA-Z0-9_.-]+$', primary_key=True, query=True, 
required=True)
diff --git a/VERSION b/VERSION
index 
07c00d000064a7687497b09524aa821dbcecc88a..2bfb2fe46b3760f30e1aa378841544a51f014728
 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=121
-# Last change: pvoborni - added server-find and server-show
+IPA_API_VERSION_MINOR=122
+# Last change: edewata - added vault-archive and vault-retrieve
diff --git a/ipalib/plugins/vault.py b/ipalib/plugins/vault.py
index 
ebb9f9fd3cf3b5a7d6b44ac9e63e122e8f71aa1a..c6fd41063058672a90a95979a907876161f3256a
 100644
--- a/ipalib/plugins/vault.py
+++ b/ipalib/plugins/vault.py
@@ -17,8 +17,21 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import base64
+import json
+import os
+import sys
+import tempfile
+
+import nss.nss as nss
+
+import pki.account
+import pki.crypto
+import pki.key
+
+from ipalib.frontend import Command
 from ipalib import api, errors
-from ipalib import Str, Flag
+from ipalib import Bytes, Str, Flag
 from ipalib import output
 from ipalib.plugable import Registry
 from ipalib.plugins.baseldap import LDAPObject, LDAPCreate, LDAPDelete,\
@@ -26,7 +39,9 @@ from ipalib.plugins.baseldap import LDAPObject, LDAPCreate, 
LDAPDelete,\
 from ipalib.request import context
 from ipalib.plugins.user import split_principal
 from ipalib import _, ngettext
+from ipaplatform.paths import paths
 from ipapython.dn import DN
+from ipapython.nsslib import current_dbdir
 
 __doc__ = _("""
 Vaults
@@ -94,6 +109,33 @@ EXAMPLES:
 """) + _("""
  Delete a user vault:
    ipa vault-del <name> --user <username>
+""") + _("""
+ Display vault configuration:
+   ipa vault-config
+""") + _("""
+ Archive data into private vault:
+   ipa vault-archive <name> --in <input file>
+""") + _("""
+ Archive data into service vault:
+   ipa vault-archive <name> --service <service name> --in <input file>
+""") + _("""
+ Archive data into shared vault:
+   ipa vault-archive <name> --shared --in <input file>
+""") + _("""
+ Archive data into user vault:
+   ipa vault-archive <name> --user <username> --in <input file>
+""") + _("""
+ Retrieve data from private vault:
+   ipa vault-retrieve <name> --out <output file>
+""") + _("""
+ Retrieve data from service vault:
+   ipa vault-retrieve <name> --service <service name> --out <output file>
+""") + _("""
+ Retrieve data from shared vault:
+   ipa vault-retrieve <name> --shared --out <output file>
+""") + _("""
+ Retrieve data from user vault:
+   ipa vault-retrieve <name> --user <user name> --out <output file>
 """)
 
 register = Registry()
@@ -243,6 +285,26 @@ class vault(LDAPObject):
         for entry in entries:
             self.backend.add_entry(entry)
 
+    def get_key_id(self, dn):
+        """
+        Generates a client key ID to archive/retrieve data in KRA.
+        """
+
+        # TODO: create container_dn after object initialization then reuse it
+        container_dn = DN(self.container_dn, self.api.env.basedn)
+
+        # make sure the DN is a vault DN
+        if not dn.endswith(container_dn, 1):
+            raise ValueError('Invalid vault DN: %s' % dn)
+
+        # construct the vault ID from the bottom up
+        id = u''
+        for rdn in dn[:-len(container_dn)]:
+            name = rdn['cn']
+            id = u'/' + name + id
+
+        return 'ipa:' + id
+
 
 @register()
 class vault_add(LDAPCreate):
@@ -273,6 +335,29 @@ class vault_del(LDAPDelete):
 
     msg_summary = _('Deleted vault "%(value)s"')
 
+    def post_callback(self, ldap, dn, *args, **options):
+        assert isinstance(dn, DN)
+
+        kra_client = self.api.Backend.kra.get_client()
+
+        kra_account = pki.account.AccountClient(kra_client.connection)
+        kra_account.login()
+
+        client_key_id = self.obj.get_key_id(dn)
+
+        # deactivate vault record in KRA
+        response = kra_client.keys.list_keys(
+            client_key_id, pki.key.KeyClient.KEY_STATUS_ACTIVE)
+
+        for key_info in response.key_infos:
+            kra_client.keys.modify_key_status(
+                key_info.get_key_id(),
+                pki.key.KeyClient.KEY_STATUS_INACTIVE)
+
+        kra_account.logout()
+
+        return True
+
 
 @register()
 class vault_find(LDAPSearch):
@@ -319,3 +404,368 @@ class vault_show(LDAPRetrieve):
     __doc__ = _('Display information about a vault.')
 
     takes_options = LDAPRetrieve.takes_options + vault_options
+
+
+@register()
+class vault_config(Command):
+    __doc__ = _('Show vault configuration.')
+
+    takes_options = (
+        Str(
+            'transport_out?',
+            doc=_('Output file to store the transport certificate'),
+        ),
+    )
+
+    has_output_params = (
+        Str(
+            'transport_cert',
+            label=_('Transport Certificate'),
+        ),
+    )
+
+    def forward(self, *args, **options):
+
+        file = options.get('transport_out')
+
+        # don't send these parameters to server
+        if 'transport_out' in options:
+            del options['transport_out']
+
+        response = super(vault_config, self).forward(*args, **options)
+
+        if file:
+            with open(file, 'w') as f:
+                f.write(response['result']['transport_cert'])
+
+        return response
+
+    def execute(self, *args, **options):
+
+        kra_client = self.api.Backend.kra.get_client()
+        transport_cert = kra_client.system_certs.get_transport_cert()
+        return {
+            'result': {
+                'transport_cert': transport_cert.encoded
+            }
+        }
+
+
+@register()
+class vault_archive(Command):
+    __doc__ = _('Archive data into a vault.')
+
+    takes_args = (
+        Str(
+            'cn',
+            cli_name='name',
+            label=_('Vault name'),
+            pattern='^[a-zA-Z0-9_.-]+$',
+            pattern_errmsg='may only include letters, numbers, _, ., and -',
+            maxlength=255,
+        ),
+    )
+
+    takes_options = vault_options + (
+        Bytes(
+            'data?',
+            doc=_('Binary data to archive'),
+        ),
+        Str(  # TODO: use File parameter
+            'in?',
+            doc=_('File containing data to archive'),
+        ),
+        Str(
+            'session_key?',
+            doc=_(
+                'Session key wrapped with transport certificate'
+                ' and encoded in base-64'),
+        ),
+        Str(
+            'vault_data?',
+            doc=_(
+                'Vault data encrypted with session key'
+                ' and encoded in base-64'),
+        ),
+        Str(
+            'nonce?',
+            doc=_('Nonce encrypted encoded in base-64'),
+        ),
+    )
+
+    msg_summary = _('Archived data into vault "%(value)s"')
+
+    def forward(self, *args, **options):
+
+        data = options.get('data')
+        input_file = options.get('in')
+
+        # don't send these parameters to server
+        if 'data' in options:
+            del options['data']
+        if 'in' in options:
+            del options['in']
+
+        # get data
+        if data and input_file:
+            raise errors.MutuallyExclusiveError(
+                reason=_('Input data specified multiple times'))
+
+        if input_file:
+            with open(input_file, 'rb') as f:
+                data = f.read()
+
+        elif not data:
+            data = ''
+
+        # initialize NSS database
+        crypto = pki.crypto.NSSCryptoProvider(paths.IPA_NSSDB_DIR)
+        crypto.initialize()
+        current_dbdir = paths.IPA_NSSDB_DIR
+
+        # retrieve transport certificate
+        (file, filename) = tempfile.mkstemp()
+        os.close(file)
+        try:
+            self.api.Command.vault_config(transport_out=unicode(filename))
+            transport_cert_der = nss.read_der_from_file(filename, True)
+            nss_transport_cert = nss.Certificate(transport_cert_der)
+
+        finally:
+            os.remove(filename)
+
+        # generate session key
+        session_key = crypto.generate_session_key()
+
+        # wrap session key with transport certificate
+        wrapped_session_key = crypto.asymmetric_wrap(
+            session_key,
+            nss_transport_cert
+        )
+
+        options['session_key'] = base64.b64encode(wrapped_session_key)\
+            .decode('utf-8')
+
+        nonce = crypto.generate_nonce_iv()
+        options['nonce'] = base64.b64encode(nonce).decode('utf-8')
+
+        vault_data = {}
+        vault_data[u'data'] = base64.b64encode(data).decode('utf-8')
+
+        json_vault_data = json.dumps(vault_data)
+
+        # wrap vault_data with session key
+        wrapped_vault_data = crypto.symmetric_wrap(
+            json_vault_data,
+            session_key,
+            nonce_iv=nonce
+        )
+
+        options['vault_data'] = base64.b64encode(wrapped_vault_data)\
+            .decode('utf-8')
+
+        return super(vault_archive, self).forward(*args, **options)
+
+    def execute(self, *args, **options):
+
+        vault_name = args[0]
+
+        # retrieve vault info
+        vault = self.api.Command.vault_show(
+            vault_name,
+            service=options.get('service'),
+            shared=options.get('shared'),
+            user=options.get('user'),
+        )['result']
+
+        # connect to KRA
+        kra_client = self.api.Backend.kra.get_client()
+
+        kra_account = pki.account.AccountClient(kra_client.connection)
+        kra_account.login()
+
+        client_key_id = self.api.Object.vault.get_key_id(vault['dn'])
+
+        # deactivate existing vault record in KRA
+        response = kra_client.keys.list_keys(
+            client_key_id,
+            pki.key.KeyClient.KEY_STATUS_ACTIVE)
+
+        for key_info in response.key_infos:
+            kra_client.keys.modify_key_status(
+                key_info.get_key_id(),
+                pki.key.KeyClient.KEY_STATUS_INACTIVE)
+
+        wrapped_session_key = base64.b64decode(options['session_key'])
+        nonce = base64.b64decode(options['nonce'])
+
+        # forward wrapped data to KRA
+        wrapped_vault_data = base64.b64decode(options['vault_data'])
+
+        kra_client.keys.archive_encrypted_data(
+            client_key_id,
+            pki.key.KeyClient.PASS_PHRASE_TYPE,
+            wrapped_vault_data,
+            wrapped_session_key,
+            None,
+            nonce,
+        )
+
+        kra_account.logout()
+
+        response = {}
+        response['result'] = {}
+
+        return response
+
+
+@register()
+class vault_retrieve(Command):
+    __doc__ = _('Retrieve a data from a vault.')
+
+    takes_args = (
+        Str(
+            'cn',
+            cli_name='name',
+            label=_('Vault name'),
+            pattern='^[a-zA-Z0-9_.-]+$',
+            pattern_errmsg='may only include letters, numbers, _, ., and -',
+            maxlength=255,
+        ),
+    )
+
+    takes_options = vault_options + (
+        Str(
+            'out?',
+            doc=_('File to store retrieved data'),
+        ),
+        Str(
+            'session_key?',
+            doc=_(
+                'Session key wrapped with transport certificate'
+                ' and encoded in base-64'),
+        ),
+    )
+
+    has_output_params = (
+        Bytes(
+            'data',
+            label=_('Data'),
+        ),
+    )
+
+    msg_summary = _('Retrieved data from vault "%(value)s"')
+
+    def forward(self, *args, **options):
+
+        output_file = options.get('out')
+
+        # don't send these parameters to server
+        if 'out' in options:
+            del options['out']
+
+        # initialize NSS database
+        crypto = pki.crypto.NSSCryptoProvider(paths.IPA_NSSDB_DIR)
+        crypto.initialize()
+        current_dbdir = paths.IPA_NSSDB_DIR
+
+        # retrieve transport certificate
+        (file, filename) = tempfile.mkstemp()
+        os.close(file)
+        try:
+            self.api.Command.vault_config(transport_out=unicode(filename))
+            transport_cert_der = nss.read_der_from_file(filename, True)
+            nss_transport_cert = nss.Certificate(transport_cert_der)
+
+        finally:
+            os.remove(filename)
+
+        # generate session key
+        session_key = crypto.generate_session_key()
+
+        # wrap session key with transport certificate
+        wrapped_session_key = crypto.asymmetric_wrap(
+            session_key,
+            nss_transport_cert
+        )
+
+        # send retrieval request to server
+        options['session_key'] = base64.b64encode(wrapped_session_key)\
+            .decode('utf-8')
+
+        response = super(vault_retrieve, self).forward(*args, **options)
+
+        result = response['result']
+        nonce = base64.b64decode(result['nonce'])
+
+        # unwrap data with session key
+        wrapped_vault_data = base64.b64decode(result['vault_data'])
+
+        json_vault_data = crypto.symmetric_unwrap(
+            wrapped_vault_data,
+            session_key,
+            nonce_iv=nonce)
+
+        vault_data = json.loads(json_vault_data)
+        data = base64.b64decode(vault_data[u'data'].encode('utf-8'))
+
+        if output_file:
+            response = {}
+            response['result'] = {}
+            with open(output_file, 'w') as f:
+                f.write(data)
+
+        else:
+            response['result']['data'] = data
+            del response['result']['nonce']
+            del response['result']['vault_data']
+
+        return response
+
+    def execute(self, *args, **options):
+
+        vault_name = args[0]
+
+        # retrieve vault info
+        vault = self.api.Command.vault_show(
+            vault_name,
+            service=options.get('service'),
+            shared=options.get('shared'),
+            user=options.get('user'),
+        )['result']
+
+        wrapped_session_key = base64.b64decode(options['session_key'])
+
+        # connect to KRA
+        kra_client = self.api.Backend.kra.get_client()
+
+        kra_account = pki.account.AccountClient(kra_client.connection)
+        kra_account.login()
+
+        client_key_id = self.api.Object.vault.get_key_id(vault['dn'])
+
+        # find vault record in KRA
+        response = kra_client.keys.list_keys(
+            client_key_id,
+            pki.key.KeyClient.KEY_STATUS_ACTIVE)
+
+        if not len(response.key_infos):
+            raise errors.NotFound(reason=_('No archived data.'))
+
+        key_info = response.key_infos[0]
+
+        # retrieve encrypted data from KRA
+        key = kra_client.keys.retrieve_key(
+            key_info.get_key_id(),
+            wrapped_session_key)
+
+        vault['vault_data'] = base64.b64encode(
+            key.encrypted_data).decode('utf-8')
+        vault['nonce'] = base64.b64encode(key.nonce_data).decode('utf-8')
+
+        kra_account.logout()
+
+        response = {}
+        response['result'] = vault
+
+        return response
diff --git a/ipatests/test_xmlrpc/test_vault_plugin.py 
b/ipatests/test_xmlrpc/test_vault_plugin.py
index 
44d397c583928d98ec252899398ae6c3a83c207c..0664addd646806f1b8a5083ef5da16c4dfc015dc
 100644
--- a/ipatests/test_xmlrpc/test_vault_plugin.py
+++ b/ipatests/test_xmlrpc/test_vault_plugin.py
@@ -22,12 +22,15 @@ Test the `ipalib/plugins/vault.py` module.
 """
 
 from ipalib import api, errors
-from xmlrpc_test import Declarative, fuzzy_string
+from xmlrpc_test import Declarative
 
 vault_name = u'test_vault'
 service_name = u'HTTP/server.example.com'
 user_name = u'testuser'
 
+# binary data from \x00 to \xff
+secret = ''.join(map(chr, xrange(0, 256)))
+
 
 class test_vault_plugin(Declarative):
 
@@ -442,4 +445,70 @@ class test_vault_plugin(Declarative):
             },
         },
 
+        {
+            'desc': 'Create vault for archival',
+            'command': (
+                'vault_add',
+                [vault_name],
+                {},
+            ),
+            'expected': {
+                'value': vault_name,
+                'summary': 'Added vault "%s"' % vault_name,
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s'
+                          % (vault_name, api.env.basedn),
+                    'objectclass': [u'top', u'ipaVault'],
+                    'cn': [vault_name],
+                },
+            },
+        },
+
+        {
+            'desc': 'Archive secret',
+            'command': (
+                'vault_archive',
+                [vault_name],
+                {
+                    'data': secret,
+                },
+            ),
+            'expected': {
+                'result': {},
+            },
+        },
+
+        {
+            'desc': 'Retrieve secret',
+            'command': (
+                'vault_retrieve',
+                [vault_name],
+                {},
+            ),
+            'expected': {
+                'result': {
+                    'dn': u'cn=%s,cn=admin,cn=users,cn=vaults,%s'
+                          % (vault_name, api.env.basedn),
+                    'cn': [vault_name],
+                    'data': secret,
+                },
+            },
+        },
+
+        {
+            'desc': 'Delete vault for archival',
+            'command': (
+                'vault_del',
+                [vault_name],
+                {},
+            ),
+            'expected': {
+                'value': [vault_name],
+                'summary': u'Deleted vault "%s"' % vault_name,
+                'result': {
+                    'failed': (),
+                },
+            },
+        },
+
     ]
-- 
1.9.3

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to