URL: https://github.com/freeipa/freeipa/pull/546
Author: simo5
 Title: #546: Store session cookie in a ccache option
Action: opened

PR body:
"""
Instead of using the kernel keyring, store the session cookie within the
ccache. This way kdestroy will really wipe away all crededntials.

Ticket: https://pagure.io/freeipa/issue/6661
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/546/head:pr546
git checkout pr546
From 8aac1aee8c10810ef1e9590b23a982ed98585f09 Mon Sep 17 00:00:00 2001
From: Simo Sorce <s...@redhat.com>
Date: Mon, 6 Mar 2017 18:47:56 -0500
Subject: [PATCH] Store session cookie in a ccache option

Instead of using the kernel keyring, store the session cookie within the
ccache. This way kdestroy will really wipe away all credentials.

Ticket: https://pagure.io/freeipa/issue/6661

Signed-off-by: Simo Sorce <s...@redhat.com>
---
 ipalib/rpc.py               |  30 ++----
 ipapython/ccache_storage.py | 234 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 242 insertions(+), 22 deletions(-)
 create mode 100644 ipapython/ccache_storage.py

diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index 8d1bba5..027a11f 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -56,7 +56,7 @@
 from ipalib.request import context, Connection
 from ipapython.ipa_log_manager import root_logger
 from ipapython import ipautil
-from ipapython import kernel_keyring
+from ipapython import ccache_storage
 from ipapython.cookie import Cookie
 from ipapython.dnsutil import DNSName
 from ipalib.text import _
@@ -84,19 +84,11 @@
     unicode = str
 
 COOKIE_NAME = 'ipa_session'
-KEYRING_COOKIE_NAME = '%s_cookie:%%s' % COOKIE_NAME
+CCACHE_COOKIE_KEY_NAME = 'X-IPA-Session-Cookie'
 
 errors_by_code = dict((e.errno, e) for e in public_errors)
 
 
-def client_session_keyring_keyname(principal):
-    '''
-    Return the key name used for storing the client session data for
-    the given principal.
-    '''
-
-    return KEYRING_COOKIE_NAME % principal
-
 def update_persistent_client_session_data(principal, data):
     '''
     Given a principal create or update the session data for that
@@ -106,13 +98,11 @@ def update_persistent_client_session_data(principal, data):
     '''
 
     try:
-        keyname = client_session_keyring_keyname(principal)
+        s = ccache_storage.session_store(CCACHE_COOKIE_KEY_NAME)
+        s.store_data(principal, data)
     except Exception as e:
         raise ValueError(str(e))
 
-    # kernel_keyring only raises ValueError (why??)
-    kernel_keyring.update_key(keyname, data)
-
 def read_persistent_client_session_data(principal):
     '''
     Given a principal return the stored session data for that
@@ -122,13 +112,11 @@ def read_persistent_client_session_data(principal):
     '''
 
     try:
-        keyname = client_session_keyring_keyname(principal)
+        s = ccache_storage.session_store(CCACHE_COOKIE_KEY_NAME)
+        return s.get_data(principal)
     except Exception as e:
         raise ValueError(str(e))
 
-    # kernel_keyring only raises ValueError (why??)
-    return kernel_keyring.read_key(keyname)
-
 def delete_persistent_client_session_data(principal):
     '''
     Given a principal remove the session data for that
@@ -138,13 +126,11 @@ def delete_persistent_client_session_data(principal):
     '''
 
     try:
-        keyname = client_session_keyring_keyname(principal)
+        s = ccache_storage.session_store(CCACHE_COOKIE_KEY_NAME)
+        s.remove_data(principal)
     except Exception as e:
         raise ValueError(str(e))
 
-    # kernel_keyring only raises ValueError (why??)
-    kernel_keyring.del_key(keyname)
-
 def xml_wrap(value, version):
     """
     Wrap all ``str`` in ``xmlrpc.client.Binary``.
diff --git a/ipapython/ccache_storage.py b/ipapython/ccache_storage.py
new file mode 100644
index 0000000..2944b33
--- /dev/null
+++ b/ipapython/ccache_storage.py
@@ -0,0 +1,234 @@
+#
+# Copyright (C) 2017  FreeIPA Contributors see COPYING for license
+#
+
+import ctypes
+import os
+import sys
+
+import six
+
+
+class KRB5Error(Exception):
+    pass
+
+
+PY3 = sys.version_info[0] == 3
+
+
+try:
+    LIBKRB5 = ctypes.CDLL('libkrb5.so.3')
+except OSError as e:  # pragma: no cover
+    LIBKRB5 = e
+else:
+    class c_text_p(ctypes.c_char_p):  # noqa
+        """A c_char_p variant that can handle UTF-8 text"""
+        @classmethod
+        def from_param(cls, value):
+            if value is None:
+                return None
+            if PY3 and isinstance(value, str):
+                return value.encode('utf-8')
+            elif not PY3 and isinstance(value, unicode):  # noqa
+                return value.encode('utf-8')
+            elif not isinstance(value, bytes):
+                raise TypeError(value)
+            else:
+                return value
+
+        @property
+        def text(self):
+            value = self.value
+            if value is None:
+                return None
+            elif not isinstance(value, str):
+                return value.decode('utf-8')
+            return value
+
+    class _krb5_context(ctypes.Structure):  # noqa
+        """krb5/krb5.h struct _krb5_context"""
+        __slots__ = ()
+        _fields_ = []
+
+    class _krb5_ccache(ctypes.Structure):  # noqa
+        """krb5/krb5.h struct _krb5_ccache"""
+        __slots__ = ()
+        _fields_ = []
+
+    class _krb5_data(ctypes.Structure):  # noqa
+        """krb5/krb5.h struct _krb5_data"""
+        __slots__ = ()
+        _fields_ = [
+            ("magic", ctypes.c_int32),
+            ("length", ctypes.c_uint),
+            ("data", ctypes.c_char_p),
+        ]
+
+    class krb5_principal_data(ctypes.Structure):  # noqa
+        """krb5/krb5.h struct krb5_principal_data"""
+        __slots__ = ()
+        _fields_ = []
+
+    def krb5_errcheck(result, func, arguments):
+        """Error checker for krb5_error return value"""
+        if result != 0:
+            raise KRB5Error(result, func.__name__, arguments)
+
+    krb5_principal = ctypes.POINTER(krb5_principal_data)
+    krb5_context = ctypes.POINTER(_krb5_context)
+    krb5_ccache = ctypes.POINTER(_krb5_ccache)
+    krb5_data_p = ctypes.POINTER(_krb5_data)
+    krb5_error = ctypes.c_int32
+
+    krb5_init_context = LIBKRB5.krb5_init_context
+    krb5_init_context.argtypes = (ctypes.POINTER(krb5_context), )
+    krb5_init_context.restype = krb5_error
+    krb5_init_context.errcheck = krb5_errcheck
+
+    krb5_free_context = LIBKRB5.krb5_free_context
+    krb5_free_context.argtypes = (krb5_context, )
+    krb5_free_context.retval = None
+
+    krb5_free_principal = LIBKRB5.krb5_free_principal
+    krb5_free_principal.argtypes = (krb5_context, krb5_principal)
+    krb5_free_principal.retval = None
+
+    krb5_free_data_contents = LIBKRB5.krb5_free_data_contents
+    krb5_free_data_contents.argtypes = (krb5_context, krb5_data_p)
+    krb5_free_data_contents.retval = None
+
+    krb5_cc_default = LIBKRB5.krb5_cc_default
+    krb5_cc_default.argtypes = (krb5_context, ctypes.POINTER(krb5_ccache), )
+    krb5_cc_default.restype = krb5_error
+    krb5_cc_default.errcheck = krb5_errcheck
+
+    krb5_cc_close = LIBKRB5.krb5_cc_close
+    krb5_cc_close.argtypes = (krb5_context, krb5_ccache, )
+    krb5_cc_close.retval = krb5_error
+    krb5_cc_close.errcheck = krb5_errcheck
+
+    krb5_parse_name = LIBKRB5.krb5_parse_name
+    krb5_parse_name.argtypes = (krb5_context, ctypes.c_char_p,
+                                ctypes.POINTER(krb5_principal), )
+    krb5_parse_name.retval = krb5_error
+    krb5_parse_name.errcheck = krb5_errcheck
+
+    krb5_cc_set_config = LIBKRB5.krb5_cc_set_config
+    krb5_cc_set_config.argtypes = (krb5_context, krb5_ccache, krb5_principal,
+                                   ctypes.c_char_p, krb5_data_p, )
+    krb5_cc_set_config.retval = krb5_error
+    krb5_cc_set_config.errcheck = krb5_errcheck
+
+    krb5_cc_get_config = LIBKRB5.krb5_cc_get_config
+    krb5_cc_get_config.argtypes = (krb5_context, krb5_ccache, krb5_principal,
+                                   ctypes.c_char_p, krb5_data_p, )
+    krb5_cc_get_config.retval = krb5_error
+    krb5_cc_get_config.errcheck = krb5_errcheck
+
+class session_store:
+    def __init__(self, name='X-IPA-Session-Cookie'):
+        self.__context = None
+        if isinstance(LIBKRB5, Exception):  # pragma: no cover
+            raise LIBKRB5
+        context = krb5_context()
+        krb5_init_context(ctypes.byref(context))
+        self.__context = context
+
+        self._hidden_cred_name = name
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, type, value, traceback):
+        if self.__context:
+            krb5_free_context(self.__context)
+            self.__context = None
+
+    def __del__(self):
+        self.__exit__(None, None, None)
+
+    def store_data(self, client, value):
+        """
+        Stores the session cookie in a hidden ccache entry.
+        """
+        assert isinstance(client, six.string_types)
+        assert isinstance(value, bytes)
+
+        principal = ccache = None
+
+        try:
+            principal = krb5_principal()
+            krb5_parse_name(self.__context, ctypes.c_char_p(client),
+                            ctypes.byref(principal))
+
+            ccache = krb5_ccache()
+            krb5_cc_default(self.__context, ctypes.byref(ccache))
+
+            buf = ctypes.create_string_buffer(value)
+            data = _krb5_data()
+            data.data = buf.value
+            data.length = len(buf)
+            krb5_cc_set_config(self.__context, ccache, principal,
+                               self._hidden_cred_name, ctypes.byref(data))
+
+        finally:
+            if principal:
+                krb5_free_principal(self.__context, principal)
+            if ccache:
+                krb5_cc_close(self.__context, ccache)
+
+    def get_data(self, client):
+        """
+        Gets the session cookie in a hidden ccache entry.
+        """
+        assert isinstance(client, six.string_types)
+
+        principal = ccache = data = None
+
+        try:
+            principal = krb5_principal()
+            krb5_parse_name(self.__context, ctypes.c_char_p(client),
+                            ctypes.byref(principal))
+
+            ccache = krb5_ccache()
+            krb5_cc_default(self.__context, ctypes.byref(ccache))
+
+            data = _krb5_data()
+            krb5_cc_get_config(self.__context, ccache, principal,
+                               self._hidden_cred_name, ctypes.byref(data))
+
+            return str(data.data)
+
+        finally:
+            if principal:
+                krb5_free_principal(self.__context, principal)
+            if ccache:
+                krb5_cc_close(self.__context, ccache)
+            if data:
+                krb5_free_data_contents(self.__context, data)
+
+    def remove_data(self, client):
+        """
+        Stores the session cookie in a hidden ccache entry.
+        """
+        assert isinstance(client, six.string_types)
+
+        principal = ccache = None
+
+        try:
+            principal = krb5_principal()
+            krb5_parse_name(self.__context, ctypes.c_char_p(client),
+                            ctypes.byref(principal))
+
+            ccache = krb5_ccache()
+            krb5_cc_default(self.__context, ctypes.byref(ccache))
+
+            krb5_cc_set_config(self.__context, ccache, principal,
+                               self._hidden_cred_name, None)
+
+        finally:
+            if principal:
+                krb5_free_principal(self.__context, principal)
+            if ccache:
+                krb5_cc_close(self.__context, ccache)
+
-- 
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