diff options
author | Simo Sorce <simo@redhat.com> | 2015-05-08 13:39:29 -0400 |
---|---|---|
committer | Jan Cholasta <jcholast@redhat.com> | 2015-10-15 14:24:33 +0200 |
commit | 463dda30679da9ac5eea5683984002989965e2a5 (patch) | |
tree | e1e6c421598e7586e47bb7d3377a19ae3f2d6d44 /ipapython/secrets/client.py | |
parent | ba22999cefb57f344acdc63a553d569ab6249099 (diff) | |
download | freeipa-463dda30679da9ac5eea5683984002989965e2a5.tar.gz freeipa-463dda30679da9ac5eea5683984002989965e2a5.tar.xz freeipa-463dda30679da9ac5eea5683984002989965e2a5.zip |
Add ipa-custodia service
Add a customized Custodia daemon and enable it after installation.
Generates server keys and loads them in LDAP autonomously on install
or update.
Provides client code classes too.
Signed-off-by: Simo Sorce <simo@redhat.com>
Reviewed-By: Jan Cholasta <jcholast@redhat.com>
Diffstat (limited to 'ipapython/secrets/client.py')
-rw-r--r-- | ipapython/secrets/client.py | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/ipapython/secrets/client.py b/ipapython/secrets/client.py new file mode 100644 index 000000000..81d066f84 --- /dev/null +++ b/ipapython/secrets/client.py @@ -0,0 +1,99 @@ +# Copyright (C) 2015 IPA Project Contributors, see COPYING for license + +from __future__ import print_function +from custodia.message.kem import KEMClient, KEY_USAGE_SIG, KEY_USAGE_ENC +from jwcrypto.common import json_decode +from jwcrypto.jwk import JWK +from ipapython.secrets.kem import IPAKEMKeys +from ipapython.secrets.store import iSecStore +from ipaplatform.paths import paths +from base64 import b64encode +import ldapurl +import gssapi +import os +import requests + + +class CustodiaClient(object): + + def _client_keys(self): + return self.ikk.server_keys + + def _server_keys(self, server, realm): + principal = 'host/%s@%s' % (server, realm) + sk = JWK(**json_decode(self.ikk.find_key(principal, KEY_USAGE_SIG))) + ek = JWK(**json_decode(self.ikk.find_key(principal, KEY_USAGE_ENC))) + return (sk, ek) + + def _ldap_uri(self, realm): + dashrealm = '-'.join(realm.split('.')) + socketpath = paths.SLAPD_INSTANCE_SOCKET_TEMPLATE % (dashrealm,) + return 'ldapi://' + ldapurl.ldapUrlEscape(socketpath) + + def _keystore(self, realm, ldap_uri, auth_type): + config = dict() + if ldap_uri is None: + config['ldap_uri'] = self._ldap_uri(realm) + else: + config['ldap_uri'] = ldap_uri + if auth_type is not None: + config['auth_type'] = auth_type + + return iSecStore(config) + + def __init__(self, client, server, realm, ldap_uri=None, auth_type=None): + self.client = client + self.creds = None + + self.service_name = gssapi.Name('HTTP@%s' % (server,), + gssapi.NameType.hostbased_service) + self.server = server + + keyfile = os.path.join(paths.IPA_CUSTODIA_CONF_DIR, 'server.keys') + self.ikk = IPAKEMKeys({'server_keys': keyfile}) + + self.kemcli = KEMClient(self._server_keys(server, realm), + self._client_keys()) + + self.keystore = self._keystore(realm, ldap_uri, auth_type) + + # FIXME: Remove warnings about missig subjAltName + requests.packages.urllib3.disable_warnings() + + def init_creds(self): + name = gssapi.Name('host@%s' % (self.client,), + gssapi.NameType.hostbased_service) + store = {'client_keytab': paths.KRB5_KEYTAB, + 'ccache': 'MEMORY:Custodia_%s' % b64encode(os.urandom(8))} + return gssapi.Credentials(name=name, store=store, usage='initiate') + + def _auth_header(self): + if not self.creds or self.creds.lifetime < 300: + self.creds = self.init_creds() + ctx = gssapi.SecurityContext(name=self.service_name, creds=self.creds) + authtok = ctx.step() + return {'Authorization': 'Negotiate %s' % b64encode(authtok)} + + def fetch_key(self, keyname): + + # Prepare URL + url = 'https://%s/ipa/keys/%s' % (self.server, keyname) + + # Prepare signed/encrypted request + encalg = ('RSA1_5', 'A256CBC-HS512') + request = self.kemcli.make_request(keyname, encalg=encalg) + + # Prepare Authentication header + headers = self._auth_header() + + # Perform request + r = requests.get(url, headers=headers, + params={'type': 'kem', 'value': request}) + r.raise_for_status() + reply = r.json() + + if 'type' not in reply or reply['type'] != 'kem': + raise RuntimeError('Invlid JSON response type') + + value = self.kemcli.parse_reply(keyname, reply['value']) + self.keystore.set('keys/%s' % keyname, value) |