These patches use git submodule to temporarily drag in the custodia and
jwcrypto python projects needed as a foundation to build the
ipa-custodia service.
This service is used for key distribution between freeipa masters. 
The keys that can be transferred are hardwired in the ipakeys module and
currently only the CA key can be transferred.
This patchset implements the last part of [1] (keys service) but a full
client is not provided with this code as radical changes to the replica
installer are needed in order to be able to use this service.

The ipa-custodia service is made automatically available upon install
and upgrade. Access to the service is mediated via Apache using a
proxy-pass directive that can be reached only after successful GSSAPI
authentication. The ipa-custodia service itself can be accessed
exclusively by the Apache user and requires that the GSS_NAME Header is
set for key recovery purposes. Setting the header in an on itself does
not grant any access to the keys. Access is granted only if
corresponding keys for the requesting princiapl are found in
cn=custodia,cn=ipa,cn=ets subtree and the signature on the request (a
JWT object) can be verified.

Once the first patch is applied developers will have to start dealing
with submodules. However unless you are developing on the submodule part
of the tree all you really need to do is to run git submodule update
--remote when a submodule is updated via a patch in master (rarely).

Simo.

-- 
Simo Sorce * Red Hat, Inc * New York
>From 42f85969adcacb654247c6963f2fe398707349fa Mon Sep 17 00:00:00 2001
From: Simo Sorce <s...@redhat.com>
Date: Tue, 26 May 2015 14:10:54 -0400
Subject: [PATCH 1/3] Temporarily add Custodia and Jwcrypto submodules

This is done to be able to build the IPA components until Jwcrypto
and Custodia packages will be available in distributions on their own.

Signed-off-by: Simo Sorce <s...@redhat.com>
---
 .gitmodules                              | 6 ++++++
 daemons/ipa-custodia/custodia            | 1 +
 daemons/ipa-custodia/jwcrypto            | 1 +
 daemons/ipa-custodia/submodules/custodia | 1 +
 daemons/ipa-custodia/submodules/jwcrypto | 1 +
 5 files changed, 10 insertions(+)
 create mode 100644 .gitmodules
 create mode 120000 daemons/ipa-custodia/custodia
 create mode 120000 daemons/ipa-custodia/jwcrypto
 create mode 160000 daemons/ipa-custodia/submodules/custodia
 create mode 160000 daemons/ipa-custodia/submodules/jwcrypto

diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000000000000000000000000000000000..0c659c53d4a4d7a03ac3b2f99e8436ad62f29d92
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "daemons/ipa-custodia/submodules/jwcrypto"]
+	path = daemons/ipa-custodia/submodules/jwcrypto
+	url = https://github.com/simo5/jwcrypto.git
+[submodule "daemons/ipa-custodia/submodules/custodia"]
+	path = daemons/ipa-custodia/submodules/custodia
+	url = https://github.com/simo5/custodia.git
diff --git a/daemons/ipa-custodia/custodia b/daemons/ipa-custodia/custodia
new file mode 120000
index 0000000000000000000000000000000000000000..a37dbe8bc834de0bcfc702d6f311d19461bcab11
--- /dev/null
+++ b/daemons/ipa-custodia/custodia
@@ -0,0 +1 @@
+submodules/custodia/custodia
\ No newline at end of file
diff --git a/daemons/ipa-custodia/jwcrypto b/daemons/ipa-custodia/jwcrypto
new file mode 120000
index 0000000000000000000000000000000000000000..4421f9eb75f72d62e630787e01f8961a8393abf9
--- /dev/null
+++ b/daemons/ipa-custodia/jwcrypto
@@ -0,0 +1 @@
+submodules/jwcrypto/jwcrypto
\ No newline at end of file
diff --git a/daemons/ipa-custodia/submodules/custodia b/daemons/ipa-custodia/submodules/custodia
new file mode 160000
index 0000000000000000000000000000000000000000..43e1f39ebe1c58408b05329644007fb1799751fa
--- /dev/null
+++ b/daemons/ipa-custodia/submodules/custodia
@@ -0,0 +1 @@
+Subproject commit 43e1f39ebe1c58408b05329644007fb1799751fa
diff --git a/daemons/ipa-custodia/submodules/jwcrypto b/daemons/ipa-custodia/submodules/jwcrypto
new file mode 160000
index 0000000000000000000000000000000000000000..a47af83ce1626533ae1f4d5402293c0ca671e811
--- /dev/null
+++ b/daemons/ipa-custodia/submodules/jwcrypto
@@ -0,0 +1 @@
+Subproject commit a47af83ce1626533ae1f4d5402293c0ca671e811
-- 
2.4.2

>From 2b518d8c9ba917e4ee38cb64ab95ff20807be772 Mon Sep 17 00:00:00 2001
From: Simo Sorce <s...@redhat.com>
Date: Fri, 8 May 2015 13:39:29 -0400
Subject: [PATCH 2/3] IPA Custodia Daemon

IPA specific plugins and configuration for using Custodia as a key
transfer mechanism.

Signed-off-by: Simo Sorce <s...@redhat.com>
---
 daemons/ipa-custodia/Makefile            |  27 ++++
 daemons/ipa-custodia/custodia.conf       |  26 ++++
 daemons/ipa-custodia/ipakeys/__init__.py |   0
 daemons/ipa-custodia/ipakeys/kem.py      | 223 +++++++++++++++++++++++++++++++
 daemons/ipa-custodia/ipakeys/store.py    | 113 ++++++++++++++++
 daemons/ipa-custodia/ipakeys/tests.py    |  61 +++++++++
 daemons/ipa-custodia/setup.py            |  18 +++
 install/updates/73-custodia.update       |   4 +
 ipaplatform/base/paths.py                |   1 +
 9 files changed, 473 insertions(+)
 create mode 100644 daemons/ipa-custodia/Makefile
 create mode 100644 daemons/ipa-custodia/custodia.conf
 create mode 100644 daemons/ipa-custodia/ipakeys/__init__.py
 create mode 100644 daemons/ipa-custodia/ipakeys/kem.py
 create mode 100644 daemons/ipa-custodia/ipakeys/store.py
 create mode 100644 daemons/ipa-custodia/ipakeys/tests.py
 create mode 100755 daemons/ipa-custodia/setup.py
 create mode 100644 install/updates/73-custodia.update

diff --git a/daemons/ipa-custodia/Makefile b/daemons/ipa-custodia/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..fbe148103f0731ee40717b71b32be6dd074289df
--- /dev/null
+++ b/daemons/ipa-custodia/Makefile
@@ -0,0 +1,27 @@
+all: lint pep8 test
+	echo "All tests passed"
+
+lint:
+	# Analyze code
+	# don't show recommendations, info, comments, report
+	# W0613 - unused argument
+	# Ignore cherrypy class members as they are dynamically added
+	pylint -d c,r,i,W0613 -r n -f colorized \
+		   --notes= \
+		   --disable=star-args \
+		   ./ipakeys
+
+pep8:
+	# Check style consistency
+	pep8 ipakeys
+
+clean:
+	rm -fr build dist *.egg-info
+	find ./ -name '*.pyc' -exec rm -f {} \;
+
+cscope:
+	git ls-files | xargs pycscope
+
+test:
+	rm -f .coverage
+	nosetests -s
diff --git a/daemons/ipa-custodia/custodia.conf b/daemons/ipa-custodia/custodia.conf
new file mode 100644
index 0000000000000000000000000000000000000000..67b4720cbbe6a9a7f302edf51ca435957404eeab
--- /dev/null
+++ b/daemons/ipa-custodia/custodia.conf
@@ -0,0 +1,26 @@
+[global]
+server_version = "IPAKeys/0.0.1"
+server_socket = /run/httpd/ipa-custodia.sock
+
+[auth:simple]
+handler = custodia.httpd.authenticators.SimpleCredsAuth
+uid = 48
+gid = 48
+
+[auth:header]
+handler = custodia.httpd.authenticators.SimpleHeaderAuth
+header = GSS_NAME
+
+[authz:kemkeys]
+handler = ipakeys.kem.IPAKEMKeys
+paths = /keys
+store = ipa
+server_keys = /etc/ipa/custodia/server.keys
+
+[store:ipa]
+handler = ipakeys.store.IPAKeys
+
+[/keys]
+handler = custodia.secrets.Secrets
+allowed_keytypes = kem
+store = ipa
diff --git a/daemons/ipa-custodia/ipakeys/__init__.py b/daemons/ipa-custodia/ipakeys/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/daemons/ipa-custodia/ipakeys/kem.py b/daemons/ipa-custodia/ipakeys/kem.py
new file mode 100644
index 0000000000000000000000000000000000000000..430f80ac20866f4b76e9166fea19ec152ec2bf34
--- /dev/null
+++ b/daemons/ipa-custodia/ipakeys/kem.py
@@ -0,0 +1,223 @@
+# Copyright (C) 2015  IPA Project Contributors, see COPYING for license
+
+from __future__ import print_function
+from ipaplatform.paths import paths
+import ConfigParser
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import rsa, ec
+from custodia.message.kem import KEMKeysStore
+from custodia.message.kem import KEY_USAGE_SIG, KEY_USAGE_ENC, KEY_USAGE_MAP
+from jwcrypto.common import json_decode, json_encode
+from jwcrypto.common import base64url_encode
+from jwcrypto.jwk import JWK
+from binascii import unhexlify
+import ldap
+import ldap.sasl
+import ldap.filter
+
+
+
+IPA_REL_BASE_DN = 'cn=custodia,cn=ipa,cn=etc'
+RFC5280_USAGE_MAP = {KEY_USAGE_SIG: 'digitalSignature',
+                     KEY_USAGE_ENC: 'dataEncipherment'}
+
+
+class KEMLdap(object):
+
+    def __init__(self, uri, auth_type=None):
+        self.uri = uri
+        if auth_type is not None:
+            self.auth_type = auth_type
+        else:
+            if uri.startswith('ldapi'):
+                self.auth_type = 'EXTERNAL'
+            else:
+                self.auth_type = 'GSSAPI'
+        conn = self.connect()
+        r = conn.search_s('', ldap.SCOPE_BASE)
+        self.basedn = r[0][1]['defaultnamingcontext'][0]
+        self.keysbase = '%s,%s' % (IPA_REL_BASE_DN, self.basedn)
+
+    def connect(self):
+        conn = ldap.initialize(self.uri)
+        if self.auth_type == 'EXTERNAL':
+            auth_tokens = ldap.sasl.external(None)
+        elif self.auth_type == 'GSSAPI':
+            auth_tokens = ldap.sasl.sasl({}, 'GSSAPI')
+        else:
+            raise ValueError(
+                'Invalid authentication type: %s' % self.auth_type)
+        conn.sasl_interactive_bind_s('', auth_tokens)
+        return conn
+
+    def _encode_int(self, i):
+        I = hex(i).rstrip("L").lstrip("0x")
+        return base64url_encode(unhexlify((len(I) % 2) * '0' + I))
+
+    def _parse_public_key(self, ipa_public_key):
+        public_key = serialization.load_der_public_key(ipa_public_key,
+                                                       default_backend())
+        num = public_key.public_numbers()
+        if isinstance(num, rsa.RSAPublicNumbers):
+            return {'kty': 'RSA',
+                    'e': self._encode_int(num.e),
+                    'n': self._encode_int(num.n)}
+        elif isinstance(num, ec.EllipticCurvePublicNumbers):
+            if num.curve.name == 'secp256r1':
+                curve = 'P-256'
+            elif num.curve.name == 'secp384r1':
+                curve = 'P-384'
+            elif num.curve.name == 'secp521r1':
+                curve = 'P-521'
+            else:
+                raise TypeError('Unsupported Elliptic Curve')
+            return {'kty': 'EC',
+                    'crv': curve,
+                    'x': self._encode_int(num.x),
+                    'y': self._encode_int(num.y)}
+        else:
+            raise TypeError('Unknown Public Key type')
+
+    def get_key(self, usage, principal):
+        conn = self.connect()
+        scope = ldap.SCOPE_SUBTREE
+        escaped_princ = ldap.filter.escape_filter_chars(principal)
+        ldap_filter = '(&(ipaKeyUsage=%s)(memberPrincipal=%s))' % (
+            RFC5280_USAGE_MAP[usage], escaped_princ)
+        r = conn.search_s(self.keysbase, scope, ldap_filter)
+        if len(r[0]) != 2:
+            raise ValueError("Incorrect number of results (%d) searhing for"
+                             "public key for %s", len(r[0]), principal)
+        ipa_public_key = r[0][1]['ipaPublicKey'][0]
+        jwk = self._parse_public_key(ipa_public_key)
+        jwk['use'] = KEY_USAGE_MAP[usage]
+        return json_encode(jwk)
+
+    def _format_public_key(self, key):
+        if isinstance(key, str):
+            jwkey = json_decode(key)
+            if 'kty' not in jwkey:
+                raise ValueError('Invalid key, missing "kty" attribute')
+            if jwkey['kty'] == 'RSA':
+                pubnum = rsa.RSAPublicNumbers(jwkey['e'], jwkey['n'])
+                pubkey = pubnum.public_key(default_backend())
+            elif jwkey['kty'] == 'EC':
+                if jwkey['crv'] == 'P-256':
+                    curve = ec.SECP256R1
+                elif jwkey['crv'] == 'P-384':
+                    curve = ec.SECP384R1
+                elif jwkey['crv'] == 'P-521':
+                    curve = ec.SECP521R1
+                else:
+                    raise TypeError('Unsupported Elliptic Curve')
+                pubnum = ec.EllipticCurvePublicNumbers(
+                    jwkey['x'], jwkey['y'], curve)
+                pubkey = pubnum.public_key(default_backend())
+            else:
+                raise ValueError('Unknown key type: %s' % jwkey['kty'])
+        elif isinstance(key, rsa.RSAPublicKey):
+            pubkey = key
+        elif isinstance(key, ec.EllipticCurvePublicKey):
+            pubkey = key
+        else:
+            raise TypeError('Unknown key type: %s' % type(key))
+
+        return pubkey.public_bytes(
+            encoding=serialization.Encoding.DER,
+            format=serialization.PublicFormat.SubjectPublicKeyInfo)
+
+    def set_key(self, usage, host, principal, key):
+        public_key = self._format_public_key(key)
+        conn = self.connect()
+        name = '%s/%s' % (KEY_USAGE_MAP[usage], host)
+        dn = 'cn=%s,%s' % (name, self.keysbase)
+        try:
+            mods = [('objectClass', ['nsContainer',
+                                     'ipaKeyPolicy',
+                                     'ipaPublicKeyObject',
+                                     'groupOfPrincipals']),
+                    ('cn', name),
+                    ('ipaKeyUsage', RFC5280_USAGE_MAP[usage]),
+                    ('memberPrincipal', principal),
+                    ('ipaPublicKey', public_key)]
+            conn.add_s(dn, mods)
+        except Exception:  # pylint: disable=broad-except
+            # This may fail if the entry already exists
+            mods = [(ldap.MOD_REPLACE, 'memberPrincipal', principal),
+                    (ldap.MOD_REPLACE, 'ipaPublicKey', public_key)]
+            conn.modify_s(dn, mods)
+
+
+def newServerKeys(path, keyid):
+    skey = JWK(generate='RSA', use='sig', kid=keyid)
+    ekey = JWK(generate='RSA', use='enc', kid=keyid)
+    with open(path, 'w+') as f:
+        f.write('[%s,%s]' % (skey.export(), ekey.export()))
+    return [skey.get_op_key('verify'), ekey.get_op_key('encrypt')]
+
+
+class IPAKEMKeys(KEMKeysStore):
+    """A KEM Keys Store.
+
+    This is a store that holds public keys of registered
+    clients allowed to use KEM messages. It takes the form
+    of an authorizer merely for the purpose of attaching
+    itself to a 'request' so that later on the KEM Parser
+    can fetch the appropariate key to verify/decrypt an
+    incoming request and make the payload available.
+
+    The KEM Parser will actually perform additional
+    authorization checks in this case.
+
+    SimplePathAuthz is extended here as we want to attach the
+    store only to requests on paths we are configured to
+    manage.
+    """
+
+    def __init__(self, config=None, ipaconf=paths.IPA_DEFAULT_CONF):
+        super(IPAKEMKeys, self).__init__(config)
+        conf = ConfigParser.ConfigParser()
+        conf.read(ipaconf)
+        self.host = conf.get('global', 'host')
+        self.realm = conf.get('global', 'realm')
+        self.ldap_uri = conf.get('global', 'ldap_uri')
+        self._server_keys = None
+
+    def find_key(self, kid, usage):
+        if kid is None:
+            raise TypeError('Key ID is None, should be a SPN')
+        conn = KEMLdap(self.ldap_uri)
+        return conn.get_key(usage, kid)
+
+    def generate_server_keys(self):
+        principal = 'host/%s@%s' % (self.host, self.realm)
+        # Neutralize the key with read if any
+        self._server_keys = None
+        # Generate private key and store it
+        pubkeys = newServerKeys(self.config['server_keys'], principal)
+        # Store public key in LDAP
+        ldapconn = KEMLdap(self.ldap_uri)
+        ldapconn.set_key(KEY_USAGE_SIG, self.host, principal, pubkeys[0])
+        ldapconn.set_key(KEY_USAGE_ENC, self.host, principal, pubkeys[1])
+
+    @property
+    def server_keys(self):
+        if self._server_keys is None:
+            with open(self.config['server_keys']) as f:
+                jsonkeys = f.read()
+            dictkeys = json_decode(jsonkeys)
+            self._server_keys = (JWK(**dictkeys[KEY_USAGE_SIG]),
+                                 JWK(**dictkeys[KEY_USAGE_ENC]))
+        return self._server_keys
+
+
+# Manual testing
+if __name__ == '__main__':
+    IKK = IPAKEMKeys({'paths': '/',
+                      'server_keys': '/etc/ipa/custodia/server.keys'})
+    IKK.generate_server_keys()
+    print(('SIG', IKK.server_keys[0].export_public()))
+    print(('ENC', IKK.server_keys[1].export_public()))
+    print(IKK.find_key('host/%s@%s' % (IKK.host, IKK.realm), usage=KEY_USAGE_SIG))
+    print(IKK.find_key('host/%s@%s' % (IKK.host, IKK.realm), usage=KEY_USAGE_ENC))
diff --git a/daemons/ipa-custodia/ipakeys/store.py b/daemons/ipa-custodia/ipakeys/store.py
new file mode 100644
index 0000000000000000000000000000000000000000..b158d5debaf0aae93bf59e5b5f5eaad854fdc6f4
--- /dev/null
+++ b/daemons/ipa-custodia/ipakeys/store.py
@@ -0,0 +1,113 @@
+# Copyright (C) 2015  IPA Project Contributors, see COPYING for license
+
+from __future__ import print_function
+from base64 import b64encode, b64decode
+from custodia.store.interface import CSStore
+from jwcrypto.common import json_decode, json_encode
+from ipaplatform.paths import paths
+from nss import nss
+import os
+import sys
+import StringIO
+
+
+class UnknownKeyName(Exception):
+    pass
+
+
+def log_error(error):
+    print(error, file=sys.stderr)
+
+
+def PKI_TOMCAT_password_callback(slot, retry, *args):
+    if retry:
+        # never retry
+        return None
+
+    password = None
+    with open(paths.PKI_TOMCAT_PASSWORD_CONF) as f:
+        for line in f.readlines():
+            key, value = line.strip().split('=')
+            if key == 'internal':
+                password = value
+                break
+    return password
+
+
+class PKI_TOMCAT_NSSDB_CERT(object):
+
+    def __init__(self, conf):
+        if 'type' not in conf or conf['type'] != 'NSSDB':
+            raise ValueError('Invalid type "%s",'
+                             ' expected "NSSDB"' % (conf['type'],))
+        if 'path' not in conf:
+            raise ValueError('Configuration does not provide NSSDB path')
+        if 'nickname' not in conf:
+            ValueError('Configuration does not provide Cert Nickname')
+        if 'pwcalback' not in conf:
+            ValueError('Configuration does not provide  Password Calback')
+        self.nssdb_path = conf['path']
+        self.nickname = conf['nickname']
+        self.ctx = nss.nss_init_context(self.nssdb_path)
+        nss.set_password_callback(conf['pwcallback'])
+
+    def export(self):
+        password = b64encode(os.urandom(16))
+        nss.pkcs12_enable_all_ciphers()
+        data = nss.pkcs12_export(self.nickname, password)
+        return json_encode({'export password': password,
+                            'pkcs12 data': b64encode(data)})
+
+    def import_key(self, value):
+        v = json_decode(value)
+        fileobj = StringIO.StringIO(b64decode(v['pkcs12 data']))
+        slot = nss.get_internal_key_slot()
+        pkcs12 = nss.PKCS12Decoder(fileobj, v['export password'], slot)
+        slot.authenticate()
+        pkcs12.database_import()
+
+
+KEY_NAME_MAP = {
+    'keys/ca/caSigningCert': {
+        'type': 'NSSDB',
+        'path': paths.PKI_TOMCAT_ALIAS_DIR,
+        'nickname': 'caSigningCert cert-pki-ca',
+        'handler': PKI_TOMCAT_NSSDB_CERT,
+        'pwcallback': PKI_TOMCAT_password_callback
+    },
+}
+
+
+class IPAKeys(CSStore):
+
+    def __init__(self, config):
+        self.config = config
+
+    def _get_conf(self, name):
+        if name not in KEY_NAME_MAP:
+            raise UnknownKeyName("Unknown key named '%s'" % name)
+        return KEY_NAME_MAP[name]
+
+    def get(self, key):
+        try:
+            conf = self._get_conf(key)
+            key_handler = conf['handler'](conf)
+            value = key_handler.export()
+        except Exception as e:  # pylint: disable=broad-except
+            log_error('Error retrievieng key "%s": %s' % (key, str(e)))
+            value = None
+        return value
+
+    def set(self, key, value, replace=False):
+        try:
+            conf = self._get_conf(key)
+            key_handler = conf['handler'](conf)
+            key_handler.import_key(value)
+        except Exception as e:  # pylint: disable=broad-except
+            log_error('Error storing key "%s": %s' % (key, str(e)))
+
+    def list(self, keyfilter=None):
+        raise NotImplementedError
+
+    def cut(self, key):
+        raise NotImplementedError
diff --git a/daemons/ipa-custodia/ipakeys/tests.py b/daemons/ipa-custodia/ipakeys/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..952ec24263ae3e7b19347bc64b578d6f2179dd3b
--- /dev/null
+++ b/daemons/ipa-custodia/ipakeys/tests.py
@@ -0,0 +1,61 @@
+# Copyright (C) 2015  FreeIPA Project Contributors - see LICENSE file
+
+from __future__ import print_function
+from ipakeys.store import IPAKeys, KEY_NAME_MAP, PKI_TOMCAT_NSSDB_CERT
+import os
+import shutil
+import subprocess
+import unittest
+
+
+def _test_pwfile_callback(slot, retry, *args):
+    if retry:
+        # never retry
+        return None
+
+    password = None
+    with open('test-ipakeys-store/pwfile') as f:
+        password = f.read()
+    return password
+
+
+class ipakeysTests(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        try:
+            shutil.rmtree('test-ipakeys-store')
+        except Exception:  # pylint: disable=broad-except
+            pass
+        testdir = 'test-ipakeys-store'
+        pwfile = os.path.join(testdir, 'pwfile')
+        os.mkdir(testdir)
+        with open(pwfile, 'w') as f:
+            f.write('testpw')
+        cls.certdb = os.path.join(testdir, 'certdb')
+        os.mkdir(cls.certdb)
+        cls.cert2db = os.path.join(testdir, 'cert2db')
+        os.mkdir(cls.cert2db)
+        seedfile = os.path.join(testdir, 'seedfile')
+        with open(seedfile, 'w') as f:
+            seed = os.urandom(1024)
+            f.write(seed)
+        subprocess.call(['certutil', '-d', cls.certdb, '-N', '-f', pwfile])
+        subprocess.call(['certutil', '-d', cls.cert2db, '-N', '-f', pwfile])
+        subprocess.call(['certutil', '-d', cls.certdb, '-S', '-f', pwfile,
+                         '-s', 'CN=testCA', '-n', 'testCACert', '-x',
+                         '-t', 'CT,C,C', '-m', '1', '-z', seedfile])
+
+    def test_ipakeys(self):
+        IK = IPAKeys({})
+
+        KEY_NAME_MAP['testCACert'] = {
+            'type': 'NSSDB',
+            'path': self.certdb,
+            'nickname': 'testCACert',
+            'handler': PKI_TOMCAT_NSSDB_CERT,
+            'pwcallback': _test_pwfile_callback
+        }
+        value = IK.get('testCACert')
+
+        KEY_NAME_MAP['testCACert']['path'] = self.cert2db
+        IK.set('testCACert', value)
diff --git a/daemons/ipa-custodia/setup.py b/daemons/ipa-custodia/setup.py
new file mode 100755
index 0000000000000000000000000000000000000000..0949983277c99bce6564158828de084b54a38d49
--- /dev/null
+++ b/daemons/ipa-custodia/setup.py
@@ -0,0 +1,18 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2015  Custodia project Contributors, for licensee see COPYING
+
+from distutils.core import setup
+
+setup(
+    name = 'ipa-custodia',
+    version = '0.0.1',
+    license = 'GPLv3+',
+    maintainer = 'IPA Project Contributors',
+    maintainer_email = 's...@redhat.com',
+    url='http://freeipa.org/',
+    packages = ['custodia', 'custodia.httpd', 'custodia.store',
+                'custodia.message', 'jwcrypto', 'ipakeys'],
+    scripts = ['custodia/custodia']
+)
+
diff --git a/install/updates/73-custodia.update b/install/updates/73-custodia.update
new file mode 100644
index 0000000000000000000000000000000000000000..f6520fb2e36dd1b234344a8cc4199ab72c664163
--- /dev/null
+++ b/install/updates/73-custodia.update
@@ -0,0 +1,4 @@
+dn: cn=custodia,cn=ipa,cn=etc,$SUFFIX
+default: objectClass: top
+default: objectClass: nsContainer
+default: cn: custodia
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index 9ba87523b5619188f02bdad6c23d2446a2c4b0f2..7589465194c6d43c52028604e925f55332d41963 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -337,6 +337,7 @@ class BasePathNamespace(object):
     ALL_SLAPD_INSTANCE_SOCKETS = "/var/run/slapd-*.socket"
     ADMIN_CERT_PATH = '/root/.dogtag/pki-tomcat/ca_admin.cert'
     ENTROPY_AVAIL = '/proc/sys/kernel/random/entropy_avail'
+    IPA_CUSTODIA_CONF = '/etc/ipa/custodia'
 
 
 
-- 
2.4.2

>From 4e96205c54c47ecf72cc8493db6f7047f20c3182 Mon Sep 17 00:00:00 2001
From: Simo Sorce <s...@redhat.com>
Date: Fri, 29 May 2015 15:53:37 -0400
Subject: [PATCH 3/3] Install ipa-custodia with the rest of ipa

Signed-off-by: Simo Sorce <s...@redhat.com>
---
 Makefile                                   |  2 ++
 daemons/ipa-custodia/ipa-custodia.service  | 13 +++++++++++++
 daemons/ipa-custodia/setup.py              |  3 ++-
 freeipa.spec.in                            | 31 ++++++++++++++++++++++++++++++
 install/conf/ipa.conf                      | 10 +++++++++-
 install/share/bootstrap-template.ldif      |  6 ++++++
 ipaplatform/base/paths.py                  |  3 +--
 ipaserver/install/custodiainstance.py      | 24 +++++++++++++++++++++++
 ipaserver/install/server/install.py        |  7 ++++++-
 ipaserver/install/server/replicainstall.py | 10 +++++++++-
 ipaserver/install/server/upgrade.py        |  4 +++-
 ipaserver/install/service.py               |  1 +
 12 files changed, 107 insertions(+), 7 deletions(-)
 create mode 100644 daemons/ipa-custodia/ipa-custodia.service
 create mode 100644 ipaserver/install/custodiainstance.py

diff --git a/Makefile b/Makefile
index abf58382960099a54b8920dd0e741b9fda17682f..73ec0e8f6b28dd117c57bfd2687acbf8a03ed680 100644
--- a/Makefile
+++ b/Makefile
@@ -178,9 +178,11 @@ server-install: server
 	if [ "$(DESTDIR)" = "" ]; then \
 		$(PYTHON) setup.py install; \
 		(cd ipaplatform && $(PYTHON) setup.py install); \
+		(cd daemons/ipa-custodia && $(PYTHON) setup.py install); \
 	else \
 		$(PYTHON) setup.py install --root $(DESTDIR); \
 		(cd ipaplatform && $(PYTHON) setup.py install --root $(DESTDIR)); \
+		(cd daemons/ipa-custodia && $(PYTHON) setup.py install --root $(DESTDIR)); \
 	fi
 
 tests: version-update tests-man-autogen
diff --git a/daemons/ipa-custodia/ipa-custodia.service b/daemons/ipa-custodia/ipa-custodia.service
new file mode 100644
index 0000000000000000000000000000000000000000..7f1cd226ecff1d1b30700c2359b053c84e58eb19
--- /dev/null
+++ b/daemons/ipa-custodia/ipa-custodia.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=IPA Custodia Service
+
+[Service]
+Type=simple
+
+ExecStart=/usr/sbin/ipa-custodia /etc/ipa/custodia/custodia.conf
+PrivateTmp=yes
+Restart=on-failure
+RestartSec=60s
+
+[Install]
+WantedBy=multi-user.target
diff --git a/daemons/ipa-custodia/setup.py b/daemons/ipa-custodia/setup.py
index 0949983277c99bce6564158828de084b54a38d49..170708efbe2fbee3bb700dc12143f61b4aabc9a9 100755
--- a/daemons/ipa-custodia/setup.py
+++ b/daemons/ipa-custodia/setup.py
@@ -13,6 +13,7 @@ setup(
     url='http://freeipa.org/',
     packages = ['custodia', 'custodia.httpd', 'custodia.store',
                 'custodia.message', 'jwcrypto', 'ipakeys'],
+    data_files = [('/usr/lib/systemd/system', ['ipa-custodia.service']),
+                  ('/etc/ipa/custodia', ['custodia.conf'])],
     scripts = ['custodia/custodia']
 )
-
diff --git a/freeipa.spec.in b/freeipa.spec.in
index 23c3d1a8005d36ce253f9979235454ba80c3dbcf..51942fd5fa502578ad0f491c54b1b921aaa2da98 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -152,6 +152,7 @@ Requires: softhsm >= 2.0.0b1-3
 Requires: p11-kit
 Requires: systemd-python
 Requires: %{etc_systemd_dir}
+Requires: python-cryptography >= 0.9
 
 Conflicts: %{alt_name}-server
 Obsoletes: %{alt_name}-server < %{version}
@@ -503,6 +504,28 @@ mkdir -p %{buildroot}%{_sysconfdir}/cron.d
 (cd %{buildroot}/%{python_sitelib}/ipatests && find . -type f  | \
     sed -e 's,\.py.*$,.*,g' | sort -u | \
     sed -e 's,\./,%%{python_sitelib}/ipatests/,g' ) >tests-python.list
+
+mv %{buildroot}/%{_bindir}/custodia %{buildroot}/%{_sbindir}/ipa-custodia
+
+(cd %{buildroot}/%{python_sitelib}/jwcrypto && find . -type f  | \
+    grep -v tests | \
+    sed -e 's,\.py.*$,.*,g' | sort -u | \
+    sed -e 's,\./,%%{python_sitelib}/jwcrypto/,g' ) >>server-python.list
+(cd %{buildroot}/%{python_sitelib}/custodia && find . -type f  | \
+    grep -v tests | \
+    sed -e 's,\.py.*$,.*,g' | sort -u | \
+    sed -e 's,\./,%%{python_sitelib}/custodia/,g' ) >>server-python.list
+(cd %{buildroot}/%{python_sitelib}/ipakeys && find . -type f  | \
+    grep -v tests | \
+    sed -e 's,\.py.*$,.*,g' | sort -u | \
+    sed -e 's,\./,%%{python_sitelib}/ipakeys/,g' ) >>server-python.list
+
+(cd %{buildroot}/%{python_sitelib}/jwcrypto && find . -type f  | \
+    grep tests | xargs rm -f)
+(cd %{buildroot}/%{python_sitelib}/ipakeys && find . -type f  | \
+    grep tests | xargs rm -f)
+
+
 %endif # ONLY_CLIENT
 
 %clean
@@ -676,6 +699,7 @@ fi
 %{_sbindir}/ipa-upgradeconfig
 %{_sbindir}/ipa-advise
 %{_sbindir}/ipa-cacert-manage
+%{_sbindir}/ipa-custodia
 %{_libexecdir}/certmonger/dogtag-ipa-ca-renew-agent-submit
 %{_libexecdir}/certmonger/ipa-server-guard
 %{_libexecdir}/ipa-otpd
@@ -700,6 +724,7 @@ fi
 %attr(644,root,root) %{_unitdir}/ipa-dnskeysyncd.service
 %attr(644,root,root) %{_unitdir}/ipa-ods-exporter.socket
 %attr(644,root,root) %{_unitdir}/ipa-ods-exporter.service
+%attr(644,root,root) %{_unitdir}/ipa-custodia.service
 %attr(644,root,root) %{etc_systemd_dir}/httpd.service
 # END
 %dir %{python_sitelib}/ipaserver
@@ -709,6 +734,10 @@ fi
 %dir %{python_sitelib}/ipaserver/advise
 %dir %{python_sitelib}/ipaserver/advise/plugins
 %dir %{python_sitelib}/ipaserver/plugins
+%dir %{python_sitelib}/jwcrypto
+%dir %{python_sitelib}/custodia
+%dir %{python_sitelib}/ipakeys
+%{python_sitelib}/ipa_custodia-*.egg-info
 %dir %{_libdir}/ipa/certmonger
 %attr(755,root,root) %{_libdir}/ipa/certmonger/*
 %dir %{_usr}/share/ipa
@@ -811,6 +840,8 @@ fi
 %ghost %{_localstatedir}/lib/ipa/pki-ca/publish
 %ghost %{_localstatedir}/named/dyndb-ldap/ipa
 %attr(755,root,root) %{_libdir}/krb5/plugins/kdb/ipadb.so
+%dir %attr(0700,root,root) %{_sysconfdir}/ipa/custodia
+%{_sysconfdir}/ipa/custodia/custodia.conf
 %{_mandir}/man1/ipa-replica-conncheck.1.gz
 %{_mandir}/man1/ipa-replica-install.1.gz
 %{_mandir}/man1/ipa-replica-manage.1.gz
diff --git a/install/conf/ipa.conf b/install/conf/ipa.conf
index 57de2f1a9543e1395f3eb46b045334f86cc8e79f..afdcdcfeae4e5f9e56cacdeb369b416cea1d3e32 100644
--- a/install/conf/ipa.conf
+++ b/install/conf/ipa.conf
@@ -1,5 +1,5 @@
 #
-# VERSION 18 - DO NOT REMOVE THIS LINE
+# VERSION 19 - DO NOT REMOVE THIS LINE
 #
 # This file may be overwritten on upgrades.
 #
@@ -103,6 +103,14 @@ WSGIScriptReloading Off
   Allow from all
 </Location>
 
+# Custodia stuff is redirected to the custodia daemon
+# after authentication
+<Location "/ipa/keys/">
+    ProxyPass "unix:/run/httpd/ipa-custodia.sock|http://localhost/keys/";
+    RequestHeader set GSS_NAME %{GSS_NAME}s
+    RequestHeader set REMOTE_USER %{REMOTE_USER}s
+</Location>
+
 # This is where we redirect on failed auth
 Alias /ipa/errors "/usr/share/ipa/html"
 
diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif
index c5d4bad8b80640881f4631e4873a12c82b0ea48a..073372e8130dc20f4c4aeebfff8ff4aa743b92ad 100644
--- a/install/share/bootstrap-template.ldif
+++ b/install/share/bootstrap-template.ldif
@@ -167,6 +167,12 @@ objectClass: nsContainer
 objectClass: top
 cn: certificates
 
+dn: cn=custodia,cn=ipa,cn=etc,$SUFFIX
+changetype: add
+objectClass: nsContainer
+objectClass: top
+cn: custodia
+
 dn: cn=s4u2proxy,cn=etc,$SUFFIX
 changetype: add
 objectClass: nsContainer
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index 7589465194c6d43c52028604e925f55332d41963..8aa69cc1f613c3b0d3e6bc6e2fd4d3e192a0ce16 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -337,8 +337,7 @@ class BasePathNamespace(object):
     ALL_SLAPD_INSTANCE_SOCKETS = "/var/run/slapd-*.socket"
     ADMIN_CERT_PATH = '/root/.dogtag/pki-tomcat/ca_admin.cert'
     ENTROPY_AVAIL = '/proc/sys/kernel/random/entropy_avail'
-    IPA_CUSTODIA_CONF = '/etc/ipa/custodia'
-
+    IPA_CUSTODIA_CONF_DIR = '/etc/ipa/custodia'
 
 
 path_namespace = BasePathNamespace
diff --git a/ipaserver/install/custodiainstance.py b/ipaserver/install/custodiainstance.py
new file mode 100644
index 0000000000000000000000000000000000000000..567e46c3add5c78d8658d587f26b4b6ce1e9de3d
--- /dev/null
+++ b/ipaserver/install/custodiainstance.py
@@ -0,0 +1,24 @@
+# Copyright (C) 2015 FreeIPa Project Contributors, see 'COPYING' for license.
+
+from ipakeys.kem import IPAKEMKeys
+from ipaplatform.paths import paths
+from service import SimpleServiceInstance
+
+class CustodiaInstance(SimpleServiceInstance):
+    def __init__(self):
+        super(CustodiaInstance, self).__init__("ipa-custodia")
+
+    def create_instance(self, *args, **kwargs):
+        self.step("Generating ipa-custodia keys", self.__gen_keys)
+        super(CustodiaInstance, self).create_instance(*args, **kwargs)
+
+    def __gen_keys(self):
+        confdir = paths.IPA_CUSTODIA_CONF_DIR
+        KeyStore = IPAKEMKeys({'server_keys': '%s/server.keys' % confdir})
+        KeyStore.generate_server_keys()
+
+    def __start(self):
+        super(CustodiaInstance, self).__start()
+
+    def __enable(self):
+        super(CustodiaInstance, self).__enable()
diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
index 7a5aa3ce45403f500544f1a663d450d957a7d638..5e98f048a0ff9ab4575d476618b268e24b6444f1 100644
--- a/ipaserver/install/server/install.py
+++ b/ipaserver/install/server/install.py
@@ -29,7 +29,7 @@ import ipaclient.ntpconf
 from ipaserver.install import (
     bindinstance, ca, cainstance, certs, dns, dsinstance, httpinstance,
     installutils, kra, krbinstance, memcacheinstance, ntpinstance,
-    otpdinstance, replication, service, sysupgrade)
+    otpdinstance, custodiainstance, replication, service, sysupgrade)
 from ipaserver.install.installutils import (
     IPA_MODULES, BadHostError, get_fqdn, get_server_ip_address,
     is_ipa_configured, load_pkcs12, read_password, verify_fqdn)
@@ -804,6 +804,10 @@ def install(installer):
     otpd.create_instance('OTPD', host_name, dm_password,
                          ipautil.realm_to_suffix(realm_name))
 
+    custodia = custodiainstance.CustodiaInstance()
+    custodia.create_instance('KEYS', host_name, dm_password,
+                             ipautil.realm_to_suffix(realm_name))
+
     # Create a HTTP instance
     http = httpinstance.HTTPInstance(fstore)
     if options.http_cert_files:
@@ -1067,6 +1071,7 @@ def uninstall(installer):
     dsinstance.DsInstance(fstore=fstore).uninstall()
     if _server_trust_ad_installed:
         adtrustinstance.ADTRUSTInstance(fstore).uninstall()
+    custodiainstance.CustodiaInstance().uninstall()
     memcacheinstance.MemcacheInstance().uninstall()
     otpdinstance.OtpdInstance().uninstall()
     tasks.restore_network_configuration(fstore, sstore)
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index 149d6b4f03502ad4184a1d142c5323296df3d35d..cab02c701213571bf2c9e86174c680f2d900c74f 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -22,7 +22,8 @@ from ipalib import api, certstore, constants, create_api, errors, x509
 import ipaclient.ntpconf
 from ipaserver.install import (
     bindinstance, ca, dns, dsinstance, httpinstance, installutils, kra,
-    krbinstance, memcacheinstance, ntpinstance, otpdinstance, service)
+    krbinstance, memcacheinstance, ntpinstance, otpdinstance, custodiainstance,
+    service)
 from ipaserver.install.installutils import create_replica_config
 from ipaserver.install.replication import (
     ReplicationManager, replica_conn_check)
@@ -591,6 +592,13 @@ def install(filename, options):
     otpd.create_instance('OTPD', config.host_name, config.dirman_password,
                          ipautil.realm_to_suffix(config.realm_name))
 
+    #FIXME: must be done earlier in replica to fetch keys for CA/ldap server
+    # before they are configured
+    custodia = custodiainstance.CustodiaInstance()
+    custodia.create_instance('KEYS', config.host_name,
+                             config.dirman_password,
+                             ipautil.realm_to_suffix(config.realm_name))
+
     # The DS instance is created before the keytab, add the SSL cert we
     # generated
     ds.add_cert_to_service()
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index c5f4d37cc02658334d5c26f269ec5dd5e386df1d..7082c91700e8dc84ae0635e76696323e340d3193 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -31,6 +31,7 @@ from ipaserver.install import service
 from ipaserver.install import cainstance
 from ipaserver.install import certs
 from ipaserver.install import otpdinstance
+from ipaserver.install import custodiainstance
 from ipaserver.install import sysupgrade
 from ipaserver.install import dnskeysyncinstance
 from ipaserver.install.upgradeinstance import IPAUpgrade
@@ -1355,13 +1356,14 @@ def upgrade_configuration():
     simple_service_list = (
         (memcacheinstance.MemcacheInstance(), 'MEMCACHE'),
         (otpdinstance.OtpdInstance(), 'OTPD'),
+        (custodiainstance.CustodiaInstance(), 'KEYS'),
     )
 
     for service, ldap_name in simple_service_list:
         service.ldapi = True
         try:
             if not service.is_configured():
-                # 389-ds needs to be running to create the memcache instance
+                # 389-ds needs to be running to create the instances
                 # because we record the new service in cn=masters.
                 ds.start()
                 service.create_instance(ldap_name, fqdn, None,
diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py
index e4e5dd3ac304676df41dff2889eead8d8e253ecf..e57b89816615f5bcccd99a7fc2e02b4b511c3dab 100644
--- a/ipaserver/install/service.py
+++ b/ipaserver/install/service.py
@@ -40,6 +40,7 @@ SERVICE_LIST = {
     'DNS': ('named', 30),
     'MEMCACHE': ('ipa_memcached', 39),
     'HTTP': ('httpd', 40),
+    'KEYS': ('ipa-custodia', 41),
     'CA': ('%sd' % dogtag.configured_constants().PKI_INSTANCE_NAME, 50),
     'ADTRUST': ('smb', 60),
     'EXTID': ('winbind', 70),
-- 
2.4.2

-- 
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