[Freeipa-devel] [freeipa PR#476][synchronized] vault: cache the transport certificate on client

2017-03-09 Thread HonzaCholasta
   URL: https://github.com/freeipa/freeipa/pull/476
Author: HonzaCholasta
 Title: #476: vault: cache the transport certificate on client
Action: synchronized

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/476/head:pr476
git checkout pr476
From 8bee187ea7f87a22594d0f67a87d9411cd678f64 Mon Sep 17 00:00:00 2001
From: Jan Cholasta 
Date: Fri, 17 Feb 2017 11:25:17 +0100
Subject: [PATCH] vault: cache the transport certificate on client

Cache the KRA transport certificate on disk (in ~/.cache/ipa) as well as
in memory.

https://fedorahosted.org/freeipa/ticket/6652
---
 ipaclient/plugins/vault.py   | 205 ++-
 ipaclient/remote_plugins/__init__.py |   3 +-
 ipaclient/remote_plugins/schema.py   |  12 +-
 ipalib/constants.py  |  14 +++
 4 files changed, 170 insertions(+), 64 deletions(-)

diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py
index 70756df..d677ec0 100644
--- a/ipaclient/plugins/vault.py
+++ b/ipaclient/plugins/vault.py
@@ -20,30 +20,38 @@
 from __future__ import print_function
 
 import base64
+import collections
+import errno
 import getpass
 import io
 import json
 import os
 import sys
+import tempfile
 
 from cryptography.fernet import Fernet, InvalidToken
 from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives import hashes, serialization
 from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
 from cryptography.hazmat.primitives.asymmetric import padding
 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 from cryptography.hazmat.primitives.padding import PKCS7
 from cryptography.hazmat.primitives.serialization import (
 load_pem_public_key, load_pem_private_key)
-from cryptography.x509 import load_der_x509_certificate
 
 from ipaclient.frontend import MethodOverride
+from ipalib import x509
+from ipalib.constants import USER_CACHE_PATH
 from ipalib.frontend import Local, Method, Object
 from ipalib.util import classproperty
 from ipalib import api, errors
 from ipalib import Bytes, Flag, Str
 from ipalib.plugable import Registry
 from ipalib import _
+from ipapython.dnsutil import DNSName
+from ipapython.ipa_log_manager import log_mgr
+
+logger = log_mgr.get_logger(__name__)
 
 
 def validated_read(argname, filename, mode='r', encoding=None):
@@ -550,6 +558,79 @@ def forward(self, *args, **options):
 return response
 
 
+class _TransportCertCache(collections.MutableMapping):
+def __init__(self):
+self._dirname = os.path.join(
+USER_CACHE_PATH, 'ipa', 'kra-transport-certs')
+self._transport_certs = {}
+
+def _get_filename(self, domain):
+basename = DNSName(domain).ToASCII() + '.pem'
+return os.path.join(self._dirname, basename)
+
+def __getitem__(self, domain):
+try:
+transport_cert = self._transport_certs[domain]
+except KeyError:
+transport_cert = None
+
+filename = self._get_filename(domain)
+try:
+try:
+transport_cert = x509.load_certificate_from_file(filename)
+except EnvironmentError as e:
+if e.errno != errno.ENOENT:
+raise
+except Exception:
+logger.warning("Failed to load %s: %s", filename,
+   exc_info=True)
+
+if transport_cert is None:
+raise KeyError(domain)
+
+self._transport_certs[domain] = transport_cert
+
+return transport_cert
+
+def __setitem__(self, domain, transport_cert):
+filename = self._get_filename(domain)
+transport_cert_der = (
+transport_cert.public_bytes(serialization.Encoding.DER))
+try:
+try:
+os.makedirs(self._dirname)
+except EnvironmentError as e:
+if e.errno != errno.EEXIST:
+raise
+fd, tmpfilename = tempfile.mkstemp(dir=self._dirname)
+os.close(fd)
+x509.write_certificate(transport_cert_der, tmpfilename)
+os.rename(tmpfilename, filename)
+except Exception:
+logger.warning("Failed to save %s", filename, exc_info=True)
+
+self._transport_certs[domain] = transport_cert
+
+def __delitem__(self, domain):
+filename = self._get_filename(domain)
+try:
+os.unlink(filename)
+except EnvironmentError as e:
+if e.errno != errno.ENOENT:
+logger.warning("Failed to remove %s", filename, exc_info=True)
+
+del self._transport_certs[domain]
+
+def __len__(self):
+return len(self._transport_certs)
+
+def __iter__(self):
+return iter(self._transport_certs)
+
+

[Freeipa-devel] [freeipa PR#476][synchronized] vault: cache the transport certificate on client

2017-03-06 Thread HonzaCholasta
   URL: https://github.com/freeipa/freeipa/pull/476
Author: HonzaCholasta
 Title: #476: vault: cache the transport certificate on client
Action: synchronized

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/476/head:pr476
git checkout pr476
From 95fe10800b5d74e610314bfab24d5935c0010a83 Mon Sep 17 00:00:00 2001
From: Jan Cholasta 
Date: Fri, 17 Feb 2017 11:25:17 +0100
Subject: [PATCH] vault: cache the transport certificate on client

Cache the KRA transport certificate on disk (in ~/.cache/ipa) as well as
in memory.

https://fedorahosted.org/freeipa/ticket/6652
---
 ipaclient/plugins/vault.py   | 205 ++-
 ipaclient/remote_plugins/__init__.py |   3 +-
 ipaclient/remote_plugins/schema.py   |  12 +-
 ipalib/constants.py  |  14 +++
 4 files changed, 170 insertions(+), 64 deletions(-)

diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py
index 70756df..d677ec0 100644
--- a/ipaclient/plugins/vault.py
+++ b/ipaclient/plugins/vault.py
@@ -20,30 +20,38 @@
 from __future__ import print_function
 
 import base64
+import collections
+import errno
 import getpass
 import io
 import json
 import os
 import sys
+import tempfile
 
 from cryptography.fernet import Fernet, InvalidToken
 from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives import hashes, serialization
 from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
 from cryptography.hazmat.primitives.asymmetric import padding
 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 from cryptography.hazmat.primitives.padding import PKCS7
 from cryptography.hazmat.primitives.serialization import (
 load_pem_public_key, load_pem_private_key)
-from cryptography.x509 import load_der_x509_certificate
 
 from ipaclient.frontend import MethodOverride
+from ipalib import x509
+from ipalib.constants import USER_CACHE_PATH
 from ipalib.frontend import Local, Method, Object
 from ipalib.util import classproperty
 from ipalib import api, errors
 from ipalib import Bytes, Flag, Str
 from ipalib.plugable import Registry
 from ipalib import _
+from ipapython.dnsutil import DNSName
+from ipapython.ipa_log_manager import log_mgr
+
+logger = log_mgr.get_logger(__name__)
 
 
 def validated_read(argname, filename, mode='r', encoding=None):
@@ -550,6 +558,79 @@ def forward(self, *args, **options):
 return response
 
 
+class _TransportCertCache(collections.MutableMapping):
+def __init__(self):
+self._dirname = os.path.join(
+USER_CACHE_PATH, 'ipa', 'kra-transport-certs')
+self._transport_certs = {}
+
+def _get_filename(self, domain):
+basename = DNSName(domain).ToASCII() + '.pem'
+return os.path.join(self._dirname, basename)
+
+def __getitem__(self, domain):
+try:
+transport_cert = self._transport_certs[domain]
+except KeyError:
+transport_cert = None
+
+filename = self._get_filename(domain)
+try:
+try:
+transport_cert = x509.load_certificate_from_file(filename)
+except EnvironmentError as e:
+if e.errno != errno.ENOENT:
+raise
+except Exception:
+logger.warning("Failed to load %s: %s", filename,
+   exc_info=True)
+
+if transport_cert is None:
+raise KeyError(domain)
+
+self._transport_certs[domain] = transport_cert
+
+return transport_cert
+
+def __setitem__(self, domain, transport_cert):
+filename = self._get_filename(domain)
+transport_cert_der = (
+transport_cert.public_bytes(serialization.Encoding.DER))
+try:
+try:
+os.makedirs(self._dirname)
+except EnvironmentError as e:
+if e.errno != errno.EEXIST:
+raise
+fd, tmpfilename = tempfile.mkstemp(dir=self._dirname)
+os.close(fd)
+x509.write_certificate(transport_cert_der, tmpfilename)
+os.rename(tmpfilename, filename)
+except Exception:
+logger.warning("Failed to save %s", filename, exc_info=True)
+
+self._transport_certs[domain] = transport_cert
+
+def __delitem__(self, domain):
+filename = self._get_filename(domain)
+try:
+os.unlink(filename)
+except EnvironmentError as e:
+if e.errno != errno.ENOENT:
+logger.warning("Failed to remove %s", filename, exc_info=True)
+
+del self._transport_certs[domain]
+
+def __len__(self):
+return len(self._transport_certs)
+
+def __iter__(self):
+return iter(self._transport_certs)
+
+

[Freeipa-devel] [freeipa PR#476][synchronized] vault: cache the transport certificate on client

2017-03-06 Thread HonzaCholasta
   URL: https://github.com/freeipa/freeipa/pull/476
Author: HonzaCholasta
 Title: #476: vault: cache the transport certificate on client
Action: synchronized

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/476/head:pr476
git checkout pr476
From b35f363fd98dd6959d1dd2f9240dcdf308606ff9 Mon Sep 17 00:00:00 2001
From: Jan Cholasta 
Date: Fri, 17 Feb 2017 11:25:17 +0100
Subject: [PATCH] vault: cache the transport certificate on client

Cache the KRA transport certificate on disk (in ~/.cache/ipa) as well as
in memory.

https://fedorahosted.org/freeipa/ticket/6652
---
 ipaclient/plugins/vault.py   | 221 ++-
 ipaclient/remote_plugins/__init__.py |   3 +-
 ipaclient/remote_plugins/schema.py   |  12 +-
 ipalib/constants.py  |  14 +++
 4 files changed, 186 insertions(+), 64 deletions(-)

diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py
index 70756df..f24ec1e 100644
--- a/ipaclient/plugins/vault.py
+++ b/ipaclient/plugins/vault.py
@@ -20,30 +20,40 @@
 from __future__ import print_function
 
 import base64
+import collections
+import errno
 import getpass
 import io
 import json
 import os
 import sys
+import tempfile
 
 from cryptography.fernet import Fernet, InvalidToken
 from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives import hashes, serialization
 from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
 from cryptography.hazmat.primitives.asymmetric import padding
 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 from cryptography.hazmat.primitives.padding import PKCS7
 from cryptography.hazmat.primitives.serialization import (
 load_pem_public_key, load_pem_private_key)
-from cryptography.x509 import load_der_x509_certificate
+
+import six
 
 from ipaclient.frontend import MethodOverride
+from ipalib import x509
+from ipalib.constants import USER_CACHE_PATH
 from ipalib.frontend import Local, Method, Object
 from ipalib.util import classproperty
 from ipalib import api, errors
 from ipalib import Bytes, Flag, Str
 from ipalib.plugable import Registry
 from ipalib import _
+from ipapython.dnsutil import DNSName
+from ipapython.ipa_log_manager import log_mgr
+
+logger = log_mgr.get_logger(__name__)
 
 
 def validated_read(argname, filename, mode='r', encoding=None):
@@ -550,6 +560,79 @@ def forward(self, *args, **options):
 return response
 
 
+class _TransportCertCache(collections.MutableMapping):
+def __init__(self):
+self._dirname = os.path.join(
+USER_CACHE_PATH, 'ipa', 'kra-transport-certs')
+self._transport_certs = {}
+
+def _get_filename(self, domain):
+basename = DNSName(domain).ToASCII() + '.pem'
+return os.path.join(self._dirname, basename)
+
+def __getitem__(self, domain):
+try:
+transport_cert = self._transport_certs[domain]
+except KeyError:
+transport_cert = None
+
+filename = self._get_filename(domain)
+try:
+try:
+transport_cert = x509.load_certificate_from_file(filename)
+except EnvironmentError as e:
+if e.errno != errno.ENOENT:
+raise
+except Exception:
+logger.warning("Failed to load %s: %s", filename,
+   exc_info=True)
+
+if transport_cert is None:
+raise KeyError(domain)
+
+self._transport_certs[domain] = transport_cert
+
+return transport_cert
+
+def __setitem__(self, domain, transport_cert):
+filename = self._get_filename(domain)
+transport_cert_der = (
+transport_cert.public_bytes(serialization.Encoding.DER))
+try:
+try:
+os.makedirs(self._dirname)
+except EnvironmentError as e:
+if e.errno != errno.EEXIST:
+raise
+fd, tmpfilename = tempfile.mkstemp(dir=self._dirname)
+os.close(fd)
+x509.write_certificate(transport_cert_der, tmpfilename)
+os.rename(tmpfilename, filename)
+except Exception:
+logger.warning("Failed to save %s", filename, exc_info=True)
+
+self._transport_certs[domain] = transport_cert
+
+def __delitem__(self, domain):
+filename = self._get_filename(domain)
+try:
+os.unlink(filename)
+except EnvironmentError as e:
+if e.errno != errno.ENOENT:
+logger.warning("Failed to remove %s", filename, exc_info=True)
+
+del self._transport_certs[domain]
+
+def __len__(self):
+return len(self._transport_certs)
+
+def __iter__(self):
+return iter(self._transport_certs)
+
+

[Freeipa-devel] [freeipa PR#476][synchronized] vault: cache the transport certificate on client

2017-02-23 Thread HonzaCholasta
   URL: https://github.com/freeipa/freeipa/pull/476
Author: HonzaCholasta
 Title: #476: vault: cache the transport certificate on client
Action: synchronized

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/476/head:pr476
git checkout pr476
From a8e1704ef4055b7d8083b99367b53dbdf95c475f Mon Sep 17 00:00:00 2001
From: Jan Cholasta 
Date: Fri, 17 Feb 2017 11:25:17 +0100
Subject: [PATCH] vault: cache the transport certificate on client

Cache the KRA transport certificate on disk (in ~/.cache/ipa) as well as
in memory.

https://fedorahosted.org/freeipa/ticket/6652
---
 ipaclient/plugins/vault.py   | 161 +++
 ipaclient/remote_plugins/__init__.py |   3 +-
 ipaclient/remote_plugins/schema.py   |  12 +--
 ipalib/constants.py  |  14 +++
 4 files changed, 143 insertions(+), 47 deletions(-)

diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py
index 9efb1f1..ebfb129 100644
--- a/ipaclient/plugins/vault.py
+++ b/ipaclient/plugins/vault.py
@@ -20,29 +20,41 @@
 from __future__ import print_function
 
 import base64
+import errno
 import getpass
 import io
 import json
 import os
 import sys
+import tempfile
 
 from cryptography.fernet import Fernet, InvalidToken
 from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives import hashes, serialization
 from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
 from cryptography.hazmat.primitives.asymmetric import padding
 from cryptography.hazmat.primitives.serialization import load_pem_public_key,\
 load_pem_private_key
 
 import nss.nss as nss
+import six
 
 from ipaclient.frontend import MethodOverride
+from ipalib import x509
+from ipalib.constants import USER_CACHE_PATH
 from ipalib.frontend import Local, Method, Object
 from ipalib.util import classproperty
 from ipalib import api, errors
 from ipalib import Bytes, Flag, Str
 from ipalib.plugable import Registry
 from ipalib import _
+from ipapython.dnsutil import DNSName
+from ipapython.ipa_log_manager import log_mgr
+
+logger = log_mgr.get_logger(__name__)
+
+TRANSPORT_CERT_CACHE_PATH = (
+os.path.join(USER_CACHE_PATH, 'ipa', 'kra-transport-certs'))
 
 
 def validated_read(argname, filename, mode='r', encoding=None):
@@ -568,6 +580,115 @@ def forward(self, *args, **options):
 return response
 
 
+class _TransportCertInvalid(Exception):
+def __init__(self, exc_info):
+self.exc_info = exc_info
+
+
+_transport_cert_cache = {}
+
+
+class ModVaultData(Local):
+def _do_internal(self, mechanism, session_key, transport_cert_der,
+ *args, **options):
+nss_transport_cert = nss.Certificate(transport_cert_der)
+
+# wrap session key with transport certificate
+# pylint: disable=no-member
+public_key = nss_transport_cert.subject_public_key_info.public_key
+# pylint: enable=no-member
+wrapped_session_key = nss.pub_wrap_sym_key(mechanism,
+   public_key,
+   session_key)
+
+options['session_key'] = wrapped_session_key.data
+
+name = self.name + '_internal'
+try:
+return self.api.Command[name](*args, **options)
+except errors.NotFound:
+raise
+except (errors.InternalError,
+errors.ExecutionError,
+errors.GenericError):
+raise _TransportCertInvalid(sys.exc_info())
+
+def internal(self, mechanism, session_key, *args, **options):
+"""
+Calls the internal counterpart of the command.
+"""
+domain = self.api.env.domain
+dirname = TRANSPORT_CERT_CACHE_PATH
+basename = DNSName(domain).ToASCII() + '.pem'
+filename = os.path.join(dirname, basename)
+
+# get transport cert from cache
+transport_cert_der = _transport_cert_cache.get(domain)
+if transport_cert_der is None:
+try:
+try:
+transport_cert = x509.load_certificate_from_file(filename)
+except EnvironmentError as e:
+if e.errno != errno.ENOENT:
+raise
+else:
+transport_cert_der = transport_cert.public_bytes(
+serialization.Encoding.DER)
+except Exception:
+logger.warning("Failed to load %s: %s", filename,
+   exc_info=True)
+
+# try call with the cached transport cert, uncache it if unsuccessful
+if transport_cert_der is not None:
+try:
+return self._do_internal(mechanism,
+ session_key,
+ transport_cert_der,
+  

[Freeipa-devel] [freeipa PR#476][synchronized] vault: cache the transport certificate on client

2017-02-22 Thread HonzaCholasta
   URL: https://github.com/freeipa/freeipa/pull/476
Author: HonzaCholasta
 Title: #476: vault: cache the transport certificate on client
Action: synchronized

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/476/head:pr476
git checkout pr476
From bc9fdf7306e1ba2cf70f812dadbf65e33b629f6d Mon Sep 17 00:00:00 2001
From: Jan Cholasta 
Date: Fri, 17 Feb 2017 11:25:17 +0100
Subject: [PATCH] vault: cache the transport certificate on client

Cache the KRA transport certificate on disk (in ~/.cache/ipa) as well as
in-memory for the lifetime of the API object.

https://fedorahosted.org/freeipa/ticket/6652
---
 ipaclient/plugins/vault.py   | 157 +++
 ipaclient/remote_plugins/__init__.py |   3 +-
 ipaclient/remote_plugins/schema.py   |  12 +--
 ipalib/constants.py  |  14 
 4 files changed, 139 insertions(+), 47 deletions(-)

diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py
index 9efb1f1..1de5cc2 100644
--- a/ipaclient/plugins/vault.py
+++ b/ipaclient/plugins/vault.py
@@ -20,29 +20,41 @@
 from __future__ import print_function
 
 import base64
+import errno
 import getpass
 import io
 import json
 import os
 import sys
+import tempfile
 
 from cryptography.fernet import Fernet, InvalidToken
 from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives import hashes, serialization
 from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
 from cryptography.hazmat.primitives.asymmetric import padding
 from cryptography.hazmat.primitives.serialization import load_pem_public_key,\
 load_pem_private_key
 
 import nss.nss as nss
+import six
 
 from ipaclient.frontend import MethodOverride
+from ipalib import x509
+from ipalib.constants import USER_CACHE_PATH
 from ipalib.frontend import Local, Method, Object
 from ipalib.util import classproperty
 from ipalib import api, errors
 from ipalib import Bytes, Flag, Str
 from ipalib.plugable import Registry
 from ipalib import _
+from ipapython.dnsutil import DNSName
+from ipapython.ipa_log_manager import log_mgr
+
+logger = log_mgr.get_logger(__name__)
+
+TRANSPORT_CERT_CACHE_PATH = (
+os.path.join(USER_CACHE_PATH, 'ipa', 'kra-transport-certs'))
 
 
 def validated_read(argname, filename, mode='r', encoding=None):
@@ -568,6 +580,111 @@ def forward(self, *args, **options):
 return response
 
 
+class _TransportCertInvalid(Exception):
+def __init__(self, exc_info):
+self.exc_info = exc_info
+
+
+_transport_cert_cache = {}
+
+
+class ModVaultData(Local):
+def _do_internal(self, mechanism, session_key, transport_cert_der,
+ *args, **options):
+nss_transport_cert = nss.Certificate(transport_cert_der)
+
+# wrap session key with transport certificate
+# pylint: disable=no-member
+public_key = nss_transport_cert.subject_public_key_info.public_key
+# pylint: enable=no-member
+wrapped_session_key = nss.pub_wrap_sym_key(mechanism,
+   public_key,
+   session_key)
+
+options['session_key'] = wrapped_session_key.data
+
+name = self.name + '_internal'
+try:
+return self.api.Command[name](*args, **options)
+except errors.NotFound:
+raise
+except (errors.InternalError,
+errors.ExecutionError,
+errors.GenericError):
+raise _TransportCertInvalid(sys.exc_info())
+
+def internal(self, mechanism, session_key, *args, **options):
+"""
+Calls the internal counterpart of the command.
+"""
+domain = self.api.env.domain
+dirname = TRANSPORT_CERT_CACHE_PATH
+basename = DNSName(domain).ToASCII() + '.pem'
+filename = os.path.join(dirname, basename)
+
+# get transport cert from cache
+transport_cert_der = _transport_cert_cache.get(domain)
+if transport_cert_der is None:
+try:
+try:
+transport_cert = x509.load_certificate_from_file(filename)
+except EnvironmentError as e:
+if e.errno != errno.ENOENT:
+raise
+else:
+transport_cert_der = transport_cert.public_bytes(
+serialization.Encoding.DER)
+except Exception:
+logger.warning("Failed to load %s: %s", filename,
+   exc_info=True)
+
+# try call with the cached transport cert, uncache it if unsuccessful
+if transport_cert_der is not None:
+try:
+return self._do_internal(mechanism,
+ session_key,
+

[Freeipa-devel] [freeipa PR#476][synchronized] vault: cache the transport certificate on client

2017-02-21 Thread HonzaCholasta
   URL: https://github.com/freeipa/freeipa/pull/476
Author: HonzaCholasta
 Title: #476: vault: cache the transport certificate on client
Action: synchronized

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/476/head:pr476
git checkout pr476
From fabb614a57d2d2eec40e846d73a0b1ba18aaf836 Mon Sep 17 00:00:00 2001
From: Jan Cholasta 
Date: Fri, 17 Feb 2017 11:25:17 +0100
Subject: [PATCH] vault: cache the transport certificate on client

https://fedorahosted.org/freeipa/ticket/6652
---
 ipaclient/plugins/vault.py   | 149 +++
 ipaclient/remote_plugins/__init__.py |   3 +-
 ipaclient/remote_plugins/schema.py   |  12 +--
 ipalib/constants.py  |  14 
 4 files changed, 131 insertions(+), 47 deletions(-)

diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py
index 9efb1f1..809f7b0 100644
--- a/ipaclient/plugins/vault.py
+++ b/ipaclient/plugins/vault.py
@@ -20,29 +20,41 @@
 from __future__ import print_function
 
 import base64
+import errno
 import getpass
 import io
 import json
 import os
 import sys
+import tempfile
 
 from cryptography.fernet import Fernet, InvalidToken
 from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives import hashes, serialization
 from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
 from cryptography.hazmat.primitives.asymmetric import padding
 from cryptography.hazmat.primitives.serialization import load_pem_public_key,\
 load_pem_private_key
 
 import nss.nss as nss
+import six
 
 from ipaclient.frontend import MethodOverride
+from ipalib import x509
+from ipalib.constants import USER_CACHE_PATH
 from ipalib.frontend import Local, Method, Object
 from ipalib.util import classproperty
 from ipalib import api, errors
 from ipalib import Bytes, Flag, Str
 from ipalib.plugable import Registry
 from ipalib import _
+from ipapython.dnsutil import DNSName
+from ipapython.ipa_log_manager import log_mgr
+
+logger = log_mgr.get_logger(__name__)
+
+TRANSPORT_CERT_CACHE_PATH = (
+os.path.join(USER_CACHE_PATH, 'ipa', 'kra-transport-certs'))
 
 
 def validated_read(argname, filename, mode='r', encoding=None):
@@ -568,6 +580,103 @@ def forward(self, *args, **options):
 return response
 
 
+class _TransportCertInvalid(Exception):
+def __init__(self, exc_info):
+self.exc_info = exc_info
+
+
+class ModVaultData(Local):
+def _do_internal(self, mechanism, session_key, transport_cert_der,
+ *args, **options):
+nss_transport_cert = nss.Certificate(transport_cert_der)
+
+# wrap session key with transport certificate
+# pylint: disable=no-member
+public_key = nss_transport_cert.subject_public_key_info.public_key
+# pylint: enable=no-member
+wrapped_session_key = nss.pub_wrap_sym_key(mechanism,
+   public_key,
+   session_key)
+
+options['session_key'] = wrapped_session_key.data
+
+name = self.name + '_internal'
+try:
+return self.api.Command[name](*args, **options)
+except errors.NotFound:
+raise
+except (errors.InternalError,
+errors.ExecutionError,
+errors.GenericError):
+raise _TransportCertInvalid(sys.exc_info())
+
+def internal(self, mechanism, session_key, *args, **options):
+"""
+Calls the internal counterpart of the command.
+"""
+dirname = TRANSPORT_CERT_CACHE_PATH
+basename = DNSName(self.api.env.domain).ToASCII() + '.pem'
+filename = os.path.join(dirname, basename)
+
+# try call with the cached transport cert, if there is one
+transport_cert = None
+try:
+try:
+transport_cert = x509.load_certificate_from_file(filename)
+except EnvironmentError as e:
+if e.errno != errno.ENOENT:
+raise
+except Exception:
+logger.warning("Failed to load %s: %s", filename,
+   exc_info=True)
+
+if transport_cert is not None:
+transport_cert_der = (
+transport_cert.public_bytes(serialization.Encoding.DER))
+
+try:
+return self._do_internal(mechanism,
+ session_key,
+ transport_cert_der,
+ *args, **options)
+except _TransportCertInvalid:
+try:
+os.remove(filename)
+except EnvironmentError:
+logger.warning("Failed to remove %s", filename,
+   exc_info=True)
+
+# 

[Freeipa-devel] [freeipa PR#476][synchronized] vault: cache the transport certificate on client

2017-02-21 Thread HonzaCholasta
   URL: https://github.com/freeipa/freeipa/pull/476
Author: HonzaCholasta
 Title: #476: vault: cache the transport certificate on client
Action: synchronized

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/476/head:pr476
git checkout pr476
From cdac87a5123b2641a19753b1dd52b741dfcc9cc3 Mon Sep 17 00:00:00 2001
From: Jan Cholasta 
Date: Fri, 17 Feb 2017 11:25:17 +0100
Subject: [PATCH] vault: cache the transport certificate on client

https://fedorahosted.org/freeipa/ticket/6652
---
 ipaclient/plugins/vault.py   | 141 ++-
 ipaclient/remote_plugins/__init__.py |   3 +-
 ipaclient/remote_plugins/schema.py   |  12 +--
 ipalib/constants.py  |  14 
 4 files changed, 123 insertions(+), 47 deletions(-)

diff --git a/ipaclient/plugins/vault.py b/ipaclient/plugins/vault.py
index 9efb1f1..91def33 100644
--- a/ipaclient/plugins/vault.py
+++ b/ipaclient/plugins/vault.py
@@ -20,29 +20,41 @@
 from __future__ import print_function
 
 import base64
+import errno
 import getpass
 import io
 import json
 import os
 import sys
+import tempfile
 
 from cryptography.fernet import Fernet, InvalidToken
 from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives import hashes, serialization
 from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
 from cryptography.hazmat.primitives.asymmetric import padding
 from cryptography.hazmat.primitives.serialization import load_pem_public_key,\
 load_pem_private_key
 
 import nss.nss as nss
+import six
 
 from ipaclient.frontend import MethodOverride
+from ipalib import x509
+from ipalib.constants import USER_CACHE_PATH
 from ipalib.frontend import Local, Method, Object
 from ipalib.util import classproperty
 from ipalib import api, errors
 from ipalib import Bytes, Flag, Str
 from ipalib.plugable import Registry
 from ipalib import _
+from ipapython.dnsutil import DNSName
+from ipapython.ipa_log_manager import log_mgr
+
+logger = log_mgr.get_logger(__name__)
+
+TRANSPORT_CERT_CACHE_PATH = (
+os.path.join(USER_CACHE_PATH, 'ipa', 'kra-transport-certs'))
 
 
 def validated_read(argname, filename, mode='r', encoding=None):
@@ -568,6 +580,95 @@ def forward(self, *args, **options):
 return response
 
 
+class _TransportCertInvalid(Exception):
+def __init__(self, exc_info):
+self.exc_info = exc_info
+
+
+class ModVaultData(Local):
+def _do_internal(self, mechanism, session_key, transport_cert_der,
+ *args, **options):
+nss_transport_cert = nss.Certificate(transport_cert_der)
+
+# wrap session key with transport certificate
+# pylint: disable=no-member
+public_key = nss_transport_cert.subject_public_key_info.public_key
+# pylint: enable=no-member
+wrapped_session_key = nss.pub_wrap_sym_key(mechanism,
+   public_key,
+   session_key)
+
+options['session_key'] = wrapped_session_key.data
+
+name = self.name + '_internal'
+try:
+return self.api.Command[name](*args, **options)
+except errors.NotFound:
+raise
+except (errors.InternalError,
+errors.ExecutionError,
+errors.GenericError):
+raise _TransportCertInvalid(sys.exc_info())
+
+def internal(self, mechanism, session_key, *args, **options):
+"""
+Calls the internal counterpart of the command.
+"""
+dirname = TRANSPORT_CERT_CACHE_PATH
+basename = DNSName(self.api.env.domain).ToASCII() + '.pem'
+filename = os.path.join(dirname, basename)
+
+# try call with the cached transport cert, if there is one
+try:
+transport_cert = x509.load_certificate_from_file(filename)
+except EnvironmentError as e:
+if e.errno != errno.ENOENT:
+logger.warning("Failed to load %s: %s", filename, e)
+else:
+transport_cert_der = (
+transport_cert.public_bytes(serialization.Encoding.DER))
+
+try:
+return self._do_internal(mechanism,
+ session_key,
+ transport_cert_der,
+ *args, **options)
+except _TransportCertInvalid:
+try:
+os.remove(filename)
+except EnvironmentError as e:
+logger.warning("Failed to remove %s: %s", filename, e)
+
+# retrieve transport certificate
+config = self.api.Command.vaultconfig_show()['result']
+transport_cert_der = config['transport_cert']
+
+# call with the retrieved transport cert, cache it if