>From a6b6c5d32c971b6976d011261d48ea9795d2481f Mon Sep 17 00:00:00 2001
From: Ade Lee <alee@redhat.com>
Date: Sun, 16 Feb 2014 15:39:37 -0500
Subject: [PATCH 195/196] Additional changes as per review.

1. Moved most methods back into the key.py module.
2. Simplified the invocation by combining the KeyClient and KeyRequestClient as just KeyClient.
3.  Added additional invocations in KRAClient - with lots of docuemntation.
These are hopefully more user friendly.
4. Extracted crypto operations to cryptoutil class.  This class has an
NSS implementation provided.
5.  Addressed other issues found in review.
---
 base/common/python/pki/cert.py                     |  57 ++-
 base/common/python/pki/cryptoutil.py               | 175 ++++++++
 base/common/python/pki/key.py                      | 253 +++++++-----
 base/common/python/pki/kraclient.py                | 442 +++++++--------------
 base/common/python/pki/systemcert.py               |  46 +++
 .../{drmclient.py => drmclient_deprecated.py}      |   7 +-
 ....readme.txt => drmclient_deprecated.readme.txt} |   6 +-
 base/kra/functional/drmtest.py                     | 130 ++++++
 base/kra/functional/drmtest.readme.txt             |  28 ++
 9 files changed, 720 insertions(+), 424 deletions(-)
 create mode 100644 base/common/python/pki/cryptoutil.py
 create mode 100644 base/common/python/pki/systemcert.py
 rename base/kra/functional/{drmclient.py => drmclient_deprecated.py} (99%)
 rename base/kra/functional/{drmclient.readme.txt => drmclient_deprecated.readme.txt} (95%)
 create mode 100644 base/kra/functional/drmtest.py
 create mode 100644 base/kra/functional/drmtest.readme.txt

diff --git a/base/common/python/pki/cert.py b/base/common/python/pki/cert.py
index 6b8994e15842218840ef5797f30fbcf81f30ab5b..0a720a5e17002b48155738dbf42501bc80750581 100644
--- a/base/common/python/pki/cert.py
+++ b/base/common/python/pki/cert.py
@@ -1,11 +1,13 @@
 #!/usr/bin/python
 '''
-Created on Aug 27, 2013
+Created on Feb 13, 2014
+Note: The implementation in this file has not been completed and is not tested.
+This note should be removed when testing/implementation is complete.
 
 @author: akoneru
 '''
 import pki.client as client
-import pki.encoder as e
+import pki.encoder as encoder
 import json
 import types
 
@@ -32,11 +34,13 @@ class CertData(object):
         ''' Constructor '''
         self.Encoded = None
 
-    def from_dict(self, attr_list):
+    @classmethod
+    def from_dict(cls, attr_list):
         ''' Return CertData object from JSON dict '''
+        cert_data = cls()
         for key in attr_list:
-            setattr(self, key, attr_list[key])
-        return self
+            setattr(cert_data, key, attr_list[key])
+        return cert_data
 
 class CertDataInfo(object):
     '''
@@ -58,11 +62,13 @@ class CertDataInfo(object):
         self.issuedOn = None
         self.issuedBy = None
 
-    def from_dict(self, attr_list):
+    @classmethod
+    def from_dict(cls, attr_list):
         ''' Return CertDataInfo object from JSON dict '''
+        cert_data_info = cls()
         for key in attr_list:
-            setattr(self, key, attr_list[key])
-        return self
+            setattr(cert_data_info, key, attr_list[key])
+        return cert_data_info
 
 class CertDataInfos(object):
     '''
@@ -75,17 +81,17 @@ class CertDataInfos(object):
         self.certInfoList = []
         self.links = []
 
-    def decode_from_json(self, json_value):
+    @classmethod
+    def from_json(cls, json_value):
         ''' Populate object from JSON input '''
+        ret = cls()
         cert_infos = json_value['CertDataInfo']
         if not isinstance(cert_infos, types.ListType):
-            certInfo = CertDataInfo()
-            self.certInfoList.append(certInfo.from_dict(cert_infos))
+            ret.certInfoList.append(CertDataInfo.from_dict(cert_infos))
         else:
             for cert_info in cert_infos:
-                cert_data_info = CertDataInfo()
-                cert_data_info.from_dict(cert_info)
-                self.certInfoList.append(cert_data_info)
+                ret.certInfoList.append(CertDataInfo.from_dict(cert_info))
+        return ret
 
 class CertSearchRequest(object):
 
@@ -134,7 +140,7 @@ class CertSearchRequest(object):
         self.certTypeInUse = False
 
 
-class CertResource(object):
+class CertClient(object):
     '''
     Class encapsulating and mirroring the functionality in the CertResouce Java interface class
     defining the REST API for Certificate resources.
@@ -153,8 +159,7 @@ class CertResource(object):
         ''' Return a CertData object for a particular certificate. '''
         url = self.cert_url + '/' + str(cert_id.id)
         response = self.connection.get(url, self.headers)
-        e.TYPES['CertData'] = CertData()
-        certData = e.CustomTypeDecoder(response.json())
+        certData = encoder.CustomTypeDecoder(response.json())
         return certData
 
     def listCerts(self, status = None):
@@ -171,13 +176,10 @@ class CertResource(object):
     def searchCerts(self, cert_search_request):
         ''' Return a CertDataInfos object containing the results of a cert search.'''
         url = self.cert_url + '/search'
-        e.TYPES['CertSearchRequest'] = CertSearchRequest
-        searchRequest = json.dumps(cert_search_request, cls=e.CustomTypeEncoder)
+        searchRequest = json.dumps(cert_search_request, cls=encoder.CustomTypeEncoder)
         r = self.connection.post(url, searchRequest, self.headers)
         print r.json()['CertDataInfos']
-        cdis = CertDataInfos()
-        cdis.decode_from_json(r.json()['CertDataInfos'])
-        return cdis
+        return CertDataInfos.from_json(r.json()['CertDataInfos'])
 
     def getCerts(self, cert_search_request):
         ''' Doctring needed here. '''
@@ -199,19 +201,14 @@ class CertResource(object):
         ''' Doc string needed here '''
         pass
 
+encoder.NOTYPES['CertData'] = CertData
+encoder.NOTYPES['CertSearchRequest'] = CertSearchRequest
 
-    def get_transport_cert(self):
-        ''' Return transport certificate '''
-        url = '/rest/config/cert/transport'
-        response = self.connection.get(url, self.headers)
-        certData = CertData()
-        certData.Encoded = response.json()['Encoded']
-        return certData.Encoded
 
 def main():
     connection = client.PKIConnection('http', 'localhost', '8080', 'ca')
     connection.authenticate('caadmin', 'Secret123')
-    certResource = CertResource(connection)
+    certResource = CertClient(connection)
     cert = certResource.getCert(CertId('0x6'))
     print cert
 
diff --git a/base/common/python/pki/cryptoutil.py b/base/common/python/pki/cryptoutil.py
new file mode 100644
index 0000000000000000000000000000000000000000..e4a01e323a4d75036baf3d521b84c7582c43f1b4
--- /dev/null
+++ b/base/common/python/pki/cryptoutil.py
@@ -0,0 +1,175 @@
+#!/usr/bin/python
+# Authors:
+#     Ade Lee <alee@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright (C) 2013 Red Hat, Inc.
+# All rights reserved.
+#
+'''
+Module containing crypto classes.
+'''
+import abc
+import base64
+import nss.nss as nss
+
+
+class CryptoUtil(object):
+    '''
+    Abstract class containing methods to do cryptographic operations.
+    '''
+    __metaclass__ = abc.ABCMeta
+
+    def __init__(self):
+        ''' Constructor '''
+        pass
+
+    @abc.abstractmethod
+    def generate_symmetric_key(self, mechanism=None):
+        ''' Generate and return a symmetric key '''
+        pass
+
+    @abc.abstractmethod
+    def symmetric_wrap(self, data, wrapping_key, mechanism=None, nonce_iv=None):
+        ''' encrypt data using a symmetric key (wrapping key)'''
+        pass
+
+    @abc.abstractmethod
+    def symmetric_unwrap(self, data, wrapping_key, mechanism=None, nonce_iv=None):
+        ''' decrypt data originally encrypted with symmetric key (wrapping key)
+
+        We expect the data and nonce_iv values to be base64 encoded.
+        The mechanism is the type of key used to do the wrapping.  It defaults
+        to a 56 bit DES3 key.
+        '''
+        pass
+
+    @abc.abstractmethod
+    def asymmetric_wrap(self, data, wrapping_cert, mechanism=None):
+        ''' encrypt a symmetric key with the public key of a transport cert.
+
+        The mechanism is the type of symmetric key, which defaults to a 56 bit
+        DES3 key.
+        '''
+        pass
+
+    #abc.abstractmethod
+    def get_cert(self, cert_nick):
+        ''' Get the certificate for the specified cert_nick. '''
+        pass
+
+class NSSCryptoUtil(CryptoUtil):
+    '''
+    Class that defines NSS implementation of CryptoUtil.
+    Requires an NSS database to have been set up and initialized.
+    '''
+
+    def __init__(self, certdb_dir, certdb_password):
+        ''' Initialize nss and nss related parameters
+
+            This method expects a NSS database to have already been created at
+            certdb_dir with password certdb_password.
+        '''
+        CryptoUtil.__init__(self)
+        self.certdb_dir = certdb_dir
+        self.certdb_password = certdb_password
+        nss.nss_init(certdb_dir)
+        self.nonce_iv = "e4:bb:3b:d3:c3:71:2e:58"
+
+    @staticmethod
+    def setup_contexts(mechanism, sym_key, nonce_iv):
+        ''' Set up contexts to do wrapping/unwrapping by symmetric keys. '''
+        # Get a PK11 slot based on the cipher
+        slot = nss.get_best_slot(mechanism)
+
+        if sym_key == None:
+            sym_key = slot.key_gen(mechanism, None, slot.get_best_key_length(mechanism))
+
+        # If initialization vector was supplied use it, otherwise set it to None
+        if nonce_iv:
+            iv_data = nss.read_hex(nonce_iv)
+            iv_si = nss.SecItem(iv_data)
+            iv_param = nss.param_from_iv(mechanism, iv_si)
+        else:
+            iv_length = nss.get_iv_length(mechanism)
+            if iv_length > 0:
+                iv_data = nss.generate_random(iv_length)
+                iv_si = nss.SecItem(iv_data)
+                iv_param = nss.param_from_iv(mechanism, iv_si)
+            else:
+                iv_param = None
+
+        # Create an encoding context
+        encoding_ctx = nss.create_context_by_sym_key(mechanism, nss.CKA_ENCRYPT,
+                                                     sym_key, iv_param)
+
+        # Create a decoding context
+        decoding_ctx = nss.create_context_by_sym_key(mechanism, nss.CKA_DECRYPT,
+                                                     sym_key, iv_param)
+
+        return encoding_ctx, decoding_ctx
+
+    def generate_symmetric_key(self, mechanism=nss.CKM_DES3_CBC_PAD):
+        ''' Returns a symmetric key.'''
+        slot = nss.get_best_slot(mechanism)
+        return slot.key_gen(mechanism, None, slot.get_best_key_length(mechanism))
+
+    def symmetric_wrap(self, data, wrapping_key, mechanism=nss.CKM_DES3_CBC_PAD, nonce_iv=None):
+        '''
+        :param data:           Data to be wrapped
+        :param wrapping_key    Symmetric key to wrap data
+
+        Wrap (encrypt) data using the supplied symmetric key
+        '''
+        encoding_ctx, _decoding_ctx = self.setup_contexts(mechanism, wrapping_key, nonce_iv)
+        wrapped_data = encoding_ctx.cipher_op(data) + encoding_ctx.digest_final()
+        return wrapped_data
+
+    def symmetric_unwrap(self, data, wrapping_key, mechanism=nss.CKM_DES3_CBC_PAD, nonce_iv=None):
+        '''
+        :param data:           Data to be unwrapped (base 64 encoded)
+        :param wrapping_key    Symmetric key to unwrap data
+        :param nonce_iv              Base 64 encoded iv data
+
+        Unwrap (decrypt) data using the supplied symmetric key
+        '''
+        if nonce_iv == None:
+            nonce_iv = self.nonce_iv
+        else:
+            nonce_iv = nss.data_to_hex(base64.decodestring(nonce_iv))
+
+        _encoding_ctx, decoding_ctx = self.setup_contexts(mechanism, wrapping_key, nonce_iv)
+        unwrapped_data = decoding_ctx.cipher_op(base64.decodestring(data)) \
+            + decoding_ctx.digest_final()
+        return unwrapped_data
+
+    def asymmetric_wrap(self, data, wrapping_cert, mechanism=nss.CKM_DES3_CBC_PAD):
+        '''
+        :param data:            Data to be wrapped
+        :param wrapping_cert    Public key to wrap data
+        :param mechanism        algorithm of symmetric key to be wrapped
+
+        Wrap (encrypt) data using the supplied asymmetric key
+        '''
+        public_key = wrapping_cert.subject_public_key_info.public_key
+        return base64.b64encode(nss.pub_wrap_sym_key(mechanism, public_key, data))
+
+    def get_cert(self, cert_nick):
+        '''
+        :param cert_nick       Nickname for the certificate to be returned
+
+        Searches NSS database and returns SecItem object for this certificate.
+        '''
+        return nss.find_cert_from_nickname(cert_nick)
diff --git a/base/common/python/pki/key.py b/base/common/python/pki/key.py
index a55696cb3416e4dd2e06947b9ffcd1839ea115f8..0572ea26409745fd4a442c098027b53b24e34722 100644
--- a/base/common/python/pki/key.py
+++ b/base/common/python/pki/key.py
@@ -20,10 +20,10 @@
 # All rights reserved.
 #
 '''
-Module containing the Python client classes for the KeyResource and
-KeyRequestResource REST API on a DRM
+Module containing the Python client classes for the KeyClient and
+KeyRequestClient REST API on a DRM
 '''
-import pki.encoder as e
+import pki.encoder as encoder
 import json
 import types
 
@@ -57,11 +57,13 @@ class KeyData(object):
         self.size = None
         self.wrappedPrivateData = None
 
-    def from_dict(self, attr_list):
+    @classmethod
+    def from_dict(cls, attr_list):
         ''' Return a KeyData object from a JSON dict '''
+        key_data = cls()
         for key in attr_list:
-            setattr(self, key, attr_list[key])
-        return self
+            setattr(key_data, key, attr_list[key])
+        return key_data
 
 class KeyInfo(object):
     '''
@@ -79,11 +81,13 @@ class KeyInfo(object):
         self.ownerName = None
         self.size = None
 
-    def from_dict(self, attr_list):
+    @classmethod
+    def from_dict(cls, attr_list):
         ''' Return KeyInfo from JSON dict '''
+        key_info = cls()
         for key in attr_list:
-            setattr(self, key, attr_list[key])
-        return self
+            setattr(key_info, key, attr_list[key])
+        return key_info
 
     def get_key_id(self):
         ''' Return the key ID as parsed from key URL '''
@@ -104,17 +108,17 @@ class KeyInfoCollection(object):
         self.key_infos = []
         self.links = []
 
-    def decode_from_json(self, json_value):
-        ''' Populate the object from its JSON representation '''
+    @classmethod
+    def from_json(cls, json_value):
+        ''' Return a KeyInfoCollection object from its JSON representation '''
+        ret = cls()
         infos = json_value['entries']
         if not isinstance(infos, types.ListType):
-            info = KeyInfo()
-            self.key_infos.append(info.from_dict(infos))
+            ret.key_infos.append(KeyInfo.from_dict(infos))
         else:
             for info in infos:
-                key_info = KeyInfo()
-                key_info.from_dict(info)
-                self.key_infos.append(key_info)
+                ret.key_infos.append(KeyInfo.from_dict(info))
+        return ret
 
 class KeyRequestInfo(object):
     '''
@@ -129,11 +133,13 @@ class KeyRequestInfo(object):
         self.keyURL = None
         self.requestStatus = None
 
-    def from_dict(self, attr_list):
+    @classmethod
+    def from_dict(cls, attr_list):
         ''' Return a KeyRequestInfo object from a JSON dict. '''
+        key_request_info = cls()
         for key in attr_list:
-            setattr(self, key, attr_list[key])
-        return self
+            setattr(key_request_info, key, attr_list[key])
+        return key_request_info
 
     def get_request_id(self):
         ''' Return the request ID by parsing the request URL. '''
@@ -161,17 +167,17 @@ class KeyRequestInfoCollection(object):
         self.key_requests = []
         self.links = []
 
-    def decode_from_json(self, json_value):
-        ''' Populate the object from its JSON representation. '''
+    @classmethod
+    def from_json(cls, json_value):
+        ''' Return a KeyRequestInfoCollection object from its JSON representation. '''
+        ret = cls()
         infos = json_value['entries']
         if not isinstance(infos, types.ListType):
-            info = KeyRequestInfo()
-            self.key_requests.append(info.from_dict(infos))
+            ret.key_requests.append(KeyRequestInfo.from_dict(infos))
         else:
             for info in infos:
-                key_request_info = KeyRequestInfo()
-                key_request_info.from_dict(info)
-                self.key_requests.append(key_request_info)
+                ret.key_requests.append(KeyRequestInfo.from_dict(info))
+        return ret
 
 class KeyRequestResponse(object):
     '''
@@ -186,10 +192,26 @@ class KeyRequestResponse(object):
         self.requestInfo = None
         self.keyData = None
 
-    def decode_from_json(self, json_value):
-        ''' Populate the object from its JSON representation. '''
-        self.requestInfo = KeyRequestInfo()
-        self.requestInfo.from_dict(json_value['RequestInfo'])
+    @classmethod
+    def from_json(cls, json_value):
+        ''' Return a KeyRequestResponse object from its JSON representation. '''
+        ret = cls()
+
+        if 'RequestInfo' in json_value:
+            ret.requestInfo = KeyRequestInfo.from_dict(json_value['RequestInfo'])
+
+        if 'KeyData' in json_value:
+            ret.keyData = KeyData.from_dict(json_value['KeyData'])
+        return ret
+
+    def get_key_id(self):
+        ''' Return the id for the key archived, recovered or generated '''
+        return self.requestInfo.get_key_id()
+
+    def get_request_id(self):
+        ''' Return the id for the created request '''
+        return self.requestInfo.get_request_id()
+
 
 class Attribute(object):
     '''
@@ -290,27 +312,33 @@ class SymKeyGenerationRequest(ResourceMessage):
     ENCRYPT_USAGE = "encrypt"
 
     def __init__(self, client_id=None, key_size=None, key_algorithm=None,
-                 key_usage=None):
+                 key_usages=None):
         ''' Constructor '''
         ResourceMessage.__init__(self,
                                  "com.netscape.certsrv.key.SymKeyGenerationRequest")
+        key_usages = key_usages or []
         self.add_attribute("clientID", client_id)
         self.add_attribute("keySize", key_size)
         self.add_attribute("keyAlgorithm", key_algorithm)
-        self.add_attribute("keyUsage", key_usage)
+        self.add_attribute("keyUsage", ','.join(key_usages))
 
-class KeyResource(object):
+class KeyClient(object):
     '''
     Class that encapsulates and mirrors the functions in the KeyResource
-    Java class in the DRM REST API.
+    and KeyRequestResource Java classes in the DRM REST API.
     '''
 
+    SYMMETRIC_KEY_TYPE = "symmetricKey"
+    PASS_PHRASE_TYPE = "passPhrase"
+    ASYMMETRIC_KEY_TYPE = "asymmetricKey"
+
     def __init__(self, connection):
         ''' Constructor '''
         self.connection = connection
         self.headers = {'Content-type': 'application/json',
                         'Accept': 'application/json'}
         self.keyURL = '/rest/agent/keys'
+        self.keyRequestsURL = '/rest/agent/keyrequests'
 
     def list_keys(self, client_id=None, status=None, max_results=None,
                   max_time=None, start=None, size=None):
@@ -323,9 +351,7 @@ class KeyResource(object):
                         'maxResults':max_results, 'maxTime':max_time,
                         'start':start, 'size':size}
         response = self.connection.get(self.keyURL, self.headers, params=query_params)
-        kdis = KeyInfoCollection()
-        kdis.decode_from_json(response.json())
-        return kdis
+        return KeyInfoCollection.from_json(response.json())
 
     def retrieve_key(self, data):
         ''' Retrieve a secret from the DRM.
@@ -338,29 +364,41 @@ class KeyResource(object):
             Returns a KeyData object containing the wrapped secret.
         '''
         url = self.keyURL + '/retrieve'
-        print url
-        e.NOTYPES['KeyRecoveryRequest'] = KeyRecoveryRequest
-        e.NOTYPES['ResourceMessage'] = ResourceMessage
-        e.NOTYPES['Attribute'] = Attribute
-        e.NOTYPES['AttributeList'] = AttributeList
-        keyRequest = json.dumps(data, cls=e.CustomTypeEncoder, sort_keys=True)
+        keyRequest = json.dumps(data, cls=encoder.CustomTypeEncoder, sort_keys=True)
         response = self.connection.post(url, keyRequest, self.headers)
-        keydata = KeyData()
-        keydata.from_dict(response.json())
-        return keydata
+        return KeyData.from_dict(response.json())
 
-class KeyRequestResource(object):
-    '''
-    Class that encapsulates and mirrors the functions in the KeyRequestResource
-    Java class in the DRM REST API/
-    '''
+    def request_key_retrieval(self, key_id, request_id, trans_wrapped_session_key=None,
+                     session_wrapped_passphrase=None, passphrase=None, nonce_data=None):
+        ''' Retrieve a secret from the DRM.
 
-    def __init__(self, connection):
-        ''' Constructor '''
-        self.connection = connection
-        self.headers = {'Content-type': 'application/json',
-                        'Accept': 'application/json'}
-        self.keyRequestsURL = '/rest/agent/keyrequests'
+            The secret (which is referenced by key_id) can be retrieved only if the
+            recovery request (referenced by request_id) is approved.  key_id and request_id
+            are required.
+
+            Data must be provided to wrap the recovered secret.  This can either be
+            a) a 56-bit DES3 symmetric key, wrapped by the DRM transport key, and
+               passed in trans_wrapped_session_key
+            b) a passphrase.  In this case, the passphrase must be wrapped by a 56-bit
+               symmetric key ("the session key" and passed in session_wrapped_passphrase,
+               and the session key must be wrapped by the DRM transport key and passed
+               in trans_wrapped_session_key
+            c) a passphrase for a p12 file.  If the key being recovered is an asymmetric
+               key, then it is possible to pass in the passphrase for the P12 file to
+               be generated.  This is passed in as passphrase
+
+            nonce_data may also be passed as a salt.
+
+            Returns a KeyData object containing the wrapped secret.
+        '''
+        request = KeyRecoveryRequest(key_id=key_id,
+                                     request_id=request_id,
+                                     trans_wrapped_session_key=trans_wrapped_session_key,
+                                     session_wrapped_passphrase=session_wrapped_passphrase,
+                                     nonce_data=nonce_data,
+                                     passphrase=passphrase)
+
+        return self.retrieve_key(request)
 
     def list_requests(self, request_state=None, request_type=None, client_id=None,
                      start=None, page_size=None, max_results=None, max_time=None):
@@ -374,17 +412,13 @@ class KeyRequestResource(object):
                         'maxResults':max_results, 'maxTime':max_time}
         response = self.connection.get(self.keyRequestsURL, self.headers,
                                 params=query_params)
-        kdis = KeyRequestInfoCollection()
-        kdis.decode_from_json(response.json())
-        return kdis
+        return KeyRequestInfoCollection.from_json(response.json())
 
     def get_request_info(self, request_id):
         ''' Return a KeyRequestInfo object for a specific request. '''
-        url = self.keyRequestsURL + '/' + request_id.value
+        url = self.keyRequestsURL + '/' + request_id
         response = self.connection.get(url, self.headers)
-        info = KeyRequestInfo()
-        info.from_dict(response.json())
-        return info
+        return KeyRequestInfo.from_dict(response.json())
 
     def create_request(self, request):
         ''' Submit an archival, recovery or key generation request
@@ -396,63 +430,92 @@ class KeyRequestResource(object):
             returns a KeyRequestResponse object.
         '''
         url = self.keyRequestsURL
-        print request
-        e.NOTYPES['SymKeyGenerationRequest'] = SymKeyGenerationRequest
-        e.NOTYPES['KeyArchivalRequest'] = KeyArchivalRequest
-        e.NOTYPES['KeyRecoveryRequest'] = KeyRecoveryRequest
-        e.NOTYPES['ResourceMessage'] = ResourceMessage
-        e.NOTYPES['Attribute'] = Attribute
-        e.NOTYPES['AttributeList'] = AttributeList
-        key_request1 = json.dumps(request, cls=e.CustomTypeEncoder, sort_keys=True)
-        print key_request1
-        response = self.connection.post(url, key_request1, self.headers)
-        key_response = KeyRequestResponse()
-        key_response.decode_from_json(response.json())
-        return key_response
+        key_request = json.dumps(request, cls=encoder.CustomTypeEncoder, sort_keys=True)
+        response = self.connection.post(url, key_request, self.headers)
+        return KeyRequestResponse.from_json(response.json())
 
     def approve_request(self, request_id):
         ''' Approve a secret recovery request '''
-        url = self.keyRequestsURL + '/' + request_id.value + '/approve'
+        url = self.keyRequestsURL + '/' + request_id + '/approve'
         return self.connection.post(url, self.headers)
 
     def reject_request(self, request_id):
         ''' Reject a secret recovery request. '''
-        url = self.keyRequestsURL + '/' + request_id.value + '/reject'
+        url = self.keyRequestsURL + '/' + request_id + '/reject'
         return self.connection.post(url, self.headers)
 
     def cancel_request(self, request_id):
         ''' Cancel a secret recovery request '''
-        url = self.keyRequestsURL + '/' + request_id.value + '/cancel'
+        url = self.keyRequestsURL + '/' + request_id + '/cancel'
         return self.connection.post(url, self.headers)
 
+    def request_recovery(self, key_id, request_id=None, session_wrapped_passphrase=None,
+                        trans_wrapped_session_key=None, b64certificate=None, nonce_data=None):
+        ''' Create a request to recover a secret.
+
+            To retrieve a symmetric key or passphrase, the only parameter that is required is
+            the keyId.  It is possible (but not required) to pass in the session keys/passphrase
+            and nonceData for the retrieval at this time.  Those parameters are documented
+            in the docstring for retrieve_key below.
+
+            To retrieve an asymmetric key, the keyId and the the base-64 encoded certificate
+            is required.
+        '''
+        request = KeyRecoveryRequest(key_id=key_id,
+                                     request_id=request_id,
+                                     trans_wrapped_session_key=trans_wrapped_session_key,
+                                     session_wrapped_passphrase=session_wrapped_passphrase,
+                                     certificate=b64certificate,
+                                     nonce_data=nonce_data)
+        return self.create_request(request)
+
+    def request_archival(self, client_id, data_type, wrapped_private_data,
+                    key_algorithm=None, key_size=None):
+        ''' Archive a secret (symmetric key or passphrase) on the DRM.
+
+            Requires a user-supplied client ID.  There can be only one active
+            key with a specified client ID.  If a record for a duplicate active
+            key exists, an exception is thrown.
+
+            data_type can be one of the following:
+
+            wrapped_private_data consists of a PKIArchiveOptions structure, which
+            can be constructed using either generate_archive_options() or
+            generate_pki_archive_options() below.
+
+            key_algorithm and key_size are applicable to symmetric keys only.
+            If a symmetric key is being archived, these parameters are required.
+        '''
+        request = KeyArchivalRequest(client_id=client_id,
+                                     data_type=data_type,
+                                     wrapped_private_data=wrapped_private_data,
+                                     key_algorithm=key_algorithm,
+                                     key_size=key_size)
+        return self.create_request(request)
+
+encoder.NOTYPES['Attribute'] = Attribute
+encoder.NOTYPES['AttributeList'] = AttributeList
+encoder.NOTYPES['KeyArchivalRequest'] = KeyArchivalRequest
+encoder.NOTYPES['KeyRecoveryRequest'] = KeyRecoveryRequest
+encoder.NOTYPES['ResourceMessage'] = ResourceMessage
+encoder.NOTYPES['SymKeyGenerationRequest'] = SymKeyGenerationRequest
 
 def main():
+    ''' Some unit tests - basically printing different types of requests '''
     print "printing symkey generation request"
     client_id = "vek 123"
     gen_request = SymKeyGenerationRequest(client_id, 128, "AES", "encrypt,decrypt")
-    e.NOTYPES['SymKeyGenerationRequest'] = SymKeyGenerationRequest
-    e.NOTYPES['ResourceMessage'] = ResourceMessage
-    e.NOTYPES['Attribute'] = Attribute
-    e.NOTYPES['AttributeList'] = AttributeList
-    print json.dumps(gen_request, cls=e.CustomTypeEncoder, sort_keys=True)
+    print json.dumps(gen_request, cls=encoder.CustomTypeEncoder, sort_keys=True)
 
     print "printing key recovery request"
     key_request = KeyRecoveryRequest("25", "MX12345BBBAAA", None,
                                      "1234ABC", None, None)
-    e.NOTYPES['KeyRecoveryRequest'] = KeyRecoveryRequest
-    e.NOTYPES['ResourceMessage'] = ResourceMessage
-    e.NOTYPES['Attribute'] = Attribute
-    e.NOTYPES['AttributeList'] = AttributeList
-    print json.dumps(key_request, cls=e.CustomTypeEncoder, sort_keys=True)
+    print json.dumps(key_request, cls=encoder.CustomTypeEncoder, sort_keys=True)
 
     print "printing key archival request"
     archival_request = KeyArchivalRequest(client_id, "symmetricKey",
                                           "MX123AABBCD", "AES", 128)
-    e.NOTYPES['KeyArchivalRequest'] = KeyArchivalRequest
-    e.NOTYPES['ResourceMessage'] = ResourceMessage
-    e.NOTYPES['Attribute'] = Attribute
-    e.NOTYPES['AttributeList'] = AttributeList
-    print json.dumps(archival_request, cls=e.CustomTypeEncoder, sort_keys=True)
+    print json.dumps(archival_request, cls=encoder.CustomTypeEncoder, sort_keys=True)
 
 if __name__ == '__main__':
     main()
diff --git a/base/common/python/pki/kraclient.py b/base/common/python/pki/kraclient.py
index 0cb6707efeeb8669bcc1a0f54331aab78d9083c5..f2b7a55820dc12a6c6013d14b4075ffd27a56cd2 100644
--- a/base/common/python/pki/kraclient.py
+++ b/base/common/python/pki/kraclient.py
@@ -21,141 +21,144 @@
 #
 '''
 Module containing KRAClient class.  This class should be used by Python clients
-to interact with the DRM to expose the functionality of the KeyResource and
+to interact with the DRM to expose the functionality of the KeyClient and
 KeyRequestResouce REST APIs.
 '''
 
-import base64
-import pki.client as client
 import pki.key as key
-import pki.cert as cert
-import nss.nss as nss
-import time
+
+from pki.systemcert import SystemCertClient
 
 class KRAClient(object):
     '''
     Client class that models interactions with a KRA using the Key and KeyRequest REST APIs.
     '''
 
-    def __init__(self, connection):
-        ''' Constructor '''
+    def __init__(self, connection, crypto, transport_cert_nick):
+        ''' Constructor
+
+        :param connection - PKIConnection object with DRM connection info.
+        :param crypto - CryptoUtil object.  NSSCryptoUtil is provided by default.
+                        If a different crypto implementation is desired, a different
+                        subclass of CryptoUtil must be provided.
+        :param trnasport_cert_nick - identifier for the DRM transport certificate.  This will
+                        be passed to the CryptoUtil.get_cert() command to get a representation
+                        of the transport certificate usable for crypto operations.
+        '''
         self.connection = connection
-        self.key_resource = key.KeyResource(connection)
-        self.key_request_resource = key.KeyRequestResource(connection)
-        self.cert_resource = cert.CertResource(connection)
+        self.keys = key.KeyClient(connection)
+        self.system_certs = SystemCertClient(connection)
+        self.crypto = crypto
+        self.transport_cert = crypto.get_cert(transport_cert_nick)
 
-        # nss parameters
-        self.certdb_dir = None
-        self.certdb_password = None
-        self.transport_nick = None
-        self.transport_cert = None
+    def retrieve_key(self, key_id, trans_wrapped_session_key=None):
+        ''' Retrieve a secret (passphrase or symmetric key) from the DRM.
 
-    def initialize_nss(self, certdb_dir, certdb_password, transport_nick):
-        ''' Initialize nss and nss related parameters
+        This function generates a key recovery request, approves it, and retrieves
+        the secret referred to by key_id.  This assumes that only one approval is required
+        to authorize the recovery.
 
-            We expect this method to be called when an nss database is to
-            be used to do client side cryptographic operations.
+        To ensure data security in transit, the data will be returned encrypted by a session
+        key (56 bit DES3 symmetric key) - which is first wrapped (encrypted) by the public
+        key of the DRM transport certificate before being sent to the DRM.  The
+        parameter trans_wrapped_session_key refers to this wrapped session key.
 
-            This method expects a NSS database to have already been created at
-            certdb_dir with password certdb_password, and the DRM transport
-            certificate to have been imported as transport_nick
-        '''
-        self.certdb_dir = certdb_dir
-        self.certdb_password = certdb_password
-        self.transport_nick = transport_nick
-        nss.nss_init(certdb_dir)
-        self.transport_cert = nss.find_cert_from_nickname(self.transport_nick)
-
-    def get_transport_cert(self):
-        ''' Return the b64 of the transport certificate. '''
-        return self.cert_resource.get_transport_cert()
-
-    def list_requests(self, request_state, request_type, start=0,
-                      page_size=100, max_results=100, max_time=10):
-        ''' Search for a list of key requests of a specified type and state.
-
-            The permitted values for request_state are:XXXX
-            The permitted values for request_type are:
-
-            Return a list of KeyRequestInfo objects '''
-        return self.key_request_resource.list_requests(request_state=request_state,
-                                                      request_type=request_type,
-                                                      start=start,
-                                                      page_size=page_size,
-                                                      max_results=max_results,
-                                                      max_time=max_time)
-    def get_request(self, request_id):
-        ''' Return a KeyRequestInfo object for a specific request '''
-        return self.key_request_resource.get_request_info(key.RequestId(request_id))
-
-    def list_keys(self, client_id, status):
-        ''' Search for secrets archived in the DRM with a given client ID and status.
-
-            The permitted values for status are: active, inactive
-            Return a list of KeyInfo objects
+        There are two ways of using this function:
+
+        1) trans_wrapped_session_key is not provided by caller.
+
+        In this case, the function will call CryptoUtil methods to generate and wrap the
+        session key.  The function will return the tuple (KeyData, unwrapped_secret)
+
+        2)  The trans_wrapped_session_key is provided by the caller.
+
+        In this case, the function will simply pass the data to the DRM, and will return the secret
+        wrapped in the session key.  The secret will still need to be unwrapped by the caller.
+
+        The function will return the tuple (KeyData, None), where the KeyData structure includes the
+        wrapped secret and some nonce data to be used as a salt when unwrapping.
         '''
-        return self.key_resource.list_keys(client_id, status)
+        key_provided = True
+        if (trans_wrapped_session_key == None):
+            key_provided = False
+            session_key = self.crypto.generate_symmetric_key()
+            trans_wrapped_session_key = self.crypto.asymmetric_wrap(session_key,
+                                                                    self.transport_cert)
+
+        response = self.keys.request_recovery(key_id)
+        request_id = response.get_request_id()
+        self.keys.approve_request(request_id)
+
+        key_data = self.keys.request_key_retrieval(key_id, request_id,
+                        trans_wrapped_session_key=trans_wrapped_session_key)
+        if key_provided:
+            return key_data, None
+
+        unwrapped_key = self.crypto.symmetric_unwrap(key_data.wrappedPrivateData, session_key,
+                                                     iv=key_data.nonceData)
+        return key_data, unwrapped_key
+
+    def retrieve_key_by_passphrase(self, key_id, passphrase=None,
+                                   trans_wrapped_session_key=None,
+                                   session_wrapped_passphrase=None,
+                                   nonce_data=None):
+        ''' Retrieve a secret (passphrase or symmetric key) from the DRM using a passphrase.
+
+        This function generates a key recovery request, approves it, and retrieves
+        the secret referred to by key_id.  This assumes that only one approval is required
+        to authorize the recovery.
+
+        The secret is secured in transit by wrapping the secret with a passphrase using
+        PBE encryption.
+
+        There are two ways of using this function:
 
-    def request_recovery(self, key_id, request_id=None, session_wrapped_passphrase=None,
-                        trans_wrapped_session_key=None, b64certificate=None, nonce_data=None):
-        ''' Create a request to recover a secret.
+        1) A passphrase is provided by the caller.
 
-            To retrieve a symmetric key or passphrase, the only parameter that is required is
-            the keyId.  It is possible (but not required) to pass in the session keys/passphrase
-            and nonceData for the retrieval at this time.  Those parameters are documented
-            in the docstring for retrieve_key below.
+        In this case, CryptoUtil methods will be called to create the data to securely send the
+        passphrase to the DRM.  Basically, three pieces of data will be sent:
 
-            To retrieve an asymmetric key, the keyId and the the base-64 encoded certificate
-            is required.
+        - the passphrase wrapped by a 56 bit DES3 symmetric key (the session key).  This
+          is referred to as the parameter session_wrapped_passphrase above.
+
+        - the session key wrapped with the public key in the DRM transport certificate.  This
+          is referred to as the trans_wrapped_session_key above.
+
+        - ivps nonce data, referred to as nonce_data
+
+        The function will return the tuple (KeyData, unwrapped_secret)
+
+        2) The caller provides the trans_wrapped_session_key, session_wrapped_passphrase
+        and nonce_data.
+
+        In this case, the data will simply be passed to the DRM.  The function will return
+        the secret encrypted by the passphrase using PBE Encryption.  The secret will still
+        need to be decrypted by the caller.
+
+        The function will return the tuple (KeyData, None)
         '''
-        request = key.KeyRecoveryRequest(key_id=key_id,
-                                         request_id=request_id,
-                                         trans_wrapped_session_key=trans_wrapped_session_key,
-                                         session_wrapped_passphrase=session_wrapped_passphrase,
-                                         certificate=b64certificate,
-                                         nonce_data=nonce_data)
-        return self.key_request_resource.create_request(request)
-
-    def approve_request(self, request_id):
-        ''' Approve a key recovery request '''
-        return self.key_request_resource.approve_request(key.RequestId(request_id))
-
-    def reject_request(self, request_id):
-        ''' Reject a key recovery request '''
-        return self.key_request_resource.reject_request(key.RequestId(request_id))
-
-    def cancel_request(self, request_id):
-        ''' Cancel a key recovery request '''
-        return self.key_request_resource.cancel_request(key.RequestId(request_id))
-
-    def retrieve_key(self, key_id, request_id, trans_wrapped_session_key=None,
-                     session_wrapped_passphrase=None, passphrase=None, nonce_data=None):
-        ''' Retrieve a secret from the DRM.
-
-            The secret (which is referenced by key_id) can be retrieved only if the
-            recovery request (referenced by request_id) is approved.  key_id and request_id
-            are required.
-
-            Data must be provided to wrap the recovered secret.  This can either be
-            a) a 56-bit DES3 symmetric key, wrapped by the DRM transport key, and
-               passed in trans_wrapped_session_key
-            b) a passphrase.  In this case, the passphrase must be wrapped by a 56-bit
-               symmetric key ("the session key" and passed in session_wrapped_passphrase,
-               and the session key must be wrapped by the DRM transport key and passed
-               in trans_wrapped_session_key
-            c) a passphrase for a p12 file.  If the key being recovered is an asymmetric
-               key, then it is possible to pass in the passphrase for the P12 file to
-               be generated.  This is passed in as passphrase
-
-            nonce_data may also be passed as a salt.
+        pass
+
+    def retrieve_key_by_pkcs12(self, key_id, certificate, passphrase):
+        ''' Retrieve an asymmetric private key and return it as PKCS12 data.
+
+        This function generates a key recovery request, approves it, and retrieves
+        the secret referred to by key_id in a PKCS12 file.  This assumes that only
+        one approval is required to authorize the recovery.
+
+        This function requires the following parameters:
+        - key_id : the ID of the key
+        - certificate: the certificate associated with the private key
+        - passphrase: A passphrase for the pkcs12 file.
+
+        The function returns a KeyData object.
         '''
-        request = key.KeyRecoveryRequest(key_id=key_id,
-                                         request_id=request_id,
-                                         trans_wrapped_session_key=trans_wrapped_session_key,
-                                         session_wrapped_passphrase=session_wrapped_passphrase,
-                                         nonce_data=nonce_data,
-                                         passphrase=passphrase)
-        return self.key_resource.retrieve_key(request)
+        response = self.keys.request_recovery(key_id, b64certificate=certificate)
+        request_id = response.get_request_id()
+        self.keys.approve_request(request_id)
+
+        return self.keys.request_key_retrieval(key_id, request_id, passphrase)
+
 
     def generate_sym_key(self, client_id, algorithm, size, usages):
         ''' Generate and archive a symmetric key on the DRM.
@@ -166,32 +169,47 @@ class KRAClient(object):
         request = key.SymKeyGenerationRequest(client_id=client_id,
                                               key_size=size,
                                               key_algorithm=algorithm,
-                                              key_usage=usages)
-        return self.key_request_resource.create_request(request)
+                                              key_usages=usages)
+        return self.keys.create_request(request)
 
-    def archive_key(self, client_id, data_type, wrapped_private_data,
+    def archive_key(self, client_id, data_type, private_data=None,
+                    wrapped_private_data=None,
                     key_algorithm=None, key_size=None):
-        ''' Archive a secret (symetric key or passphrase) on the DRM.
+        ''' Archive a secret (symmetric key or passphrase) on the DRM.
 
             Requires a user-supplied client ID.  There can be only one active
             key with a specified client ID.  If a record for a duplicate active
-            key exists, an exception is thrown.
+            key exists, a BadRequestException is thrown.
 
             data_type can be one of the following:
+                KeyRequestResource.SYMMETRIC_KEY_TYPE,
+                KeyRequestResource.ASYMMETRIC_KEY_TYPE,
+                KeyRequestResource.PASS_PHRASE_TYPE
+
+            key_algorithm and key_size are applicable to symmetric keys only.
+            If a symmetric key is being archived, these parameters are required.
 
             wrapped_private_data consists of a PKIArchiveOptions structure, which
             can be constructed using either generate_archive_options() or
             generate_pki_archive_options() below.
 
-            key_algorithm and key_size are applicable to symmetric keys only.
-            If a symmetric key is being archived, these parameters are required.
+            private_data is the secret that is to be archived.
+
+            Callers must specify EITHER wrapped_private_data OR private_data.
+            If wrapped_private_data is specified, then this data is forwarded to the
+            DRM unchanged.  Otherwise, the private_data is converted to a
+            PKIArchiveOptions structure using the functions below.
+
+            The function returns a KeyRequestResponse object containing a KeyRequestInfo
+            object with details about the archival request and key archived.
         '''
-        request = key.KeyArchivalRequest(client_id=client_id,
-                                         data_type=data_type,
-                                         wrapped_private_data=wrapped_private_data,
-                                         key_algorithm=key_algorithm,
-                                         key_size=key_size)
-        return self.key_request_resource.create_request(request)
+        if wrapped_private_data == None:
+            if private_data == None:
+                # raise BadRequestException - to be added in next patch
+                return None
+            wrapped_private_data = self.generate_archive_options(private_data)
+        return self.keys.request_archival(client_id, data_type, wrapped_private_data,
+                                          key_algorithm, key_size)
 
     def generate_pki_archive_options(self, trans_wrapped_session_key, session_wrapped_secret):
         ''' Return a PKIArchiveOptions structure for archiving a secret
@@ -202,38 +220,6 @@ class KRAClient(object):
         '''
         pass
 
-    def setup_contexts(self, mechanism, sym_key, iv_vector):
-        ''' Set up contexts to do wrapping/unwrapping by symmetric keys. '''
-        # Get a PK11 slot based on the cipher
-        slot = nss.get_best_slot(mechanism)
-
-        if sym_key == None:
-            sym_key = slot.key_gen(mechanism, None, slot.get_best_key_length(mechanism))
-
-        # If initialization vector was supplied use it, otherwise set it to None
-        if iv_vector:
-            iv_data = nss.read_hex(iv_vector)
-            iv_si = nss.SecItem(iv_data)
-            iv_param = nss.param_from_iv(mechanism, iv_si)
-        else:
-            iv_length = nss.get_iv_length(mechanism)
-            if iv_length > 0:
-                iv_data = nss.generate_random(iv_length)
-                iv_si = nss.SecItem(iv_data)
-                iv_param = nss.param_from_iv(mechanism, iv_si)
-            else:
-                iv_param = None
-
-        # Create an encoding context
-        encoding_ctx = nss.create_context_by_sym_key(mechanism, nss.CKA_ENCRYPT,
-                                                     sym_key, iv_param)
-
-        # Create a decoding context
-        decoding_ctx = nss.create_context_by_sym_key(mechanism, nss.CKA_DECRYPT,
-                                                     sym_key, iv_param)
-
-        return encoding_ctx, decoding_ctx
-
     def generate_archive_options(self, secret):
         ''' Return a PKIArchiveOptions structure for archiving a secret.
 
@@ -246,141 +232,9 @@ class KRAClient(object):
 
             This method expects initialize_nss() to have been called previously.
         '''
-        mechanism = nss.CKM_DES3_CBC_PAD
-        slot = nss.get_best_slot(mechanism)
-        session_key = slot.key_gen(mechanism, None, slot.get_best_key_length(mechanism))
-
-        public_key = self.transport_cert.subject_public_key_info.public_key
-        trans_wrapped_session_key = base64.b64encode(nss.pub_wrap_sym_key(
-                                        mechanism, public_key, session_key))
-
-        encoding_ctx, _decoding_ctx = self.setup_contexts(mechanism, session_key, None)
-        wrapped_secret = encoding_ctx.cipher_op(secret) + encoding_ctx.digest_final()
+        session_key = self.crypto.generate_symmetric_key()
+        trans_wrapped_session_key = self.crypto.asymmetric_wrap(session_key, self.transport_cert)
+        wrapped_secret = self.crypto.symmetric_wrap(secret, session_key)
 
         return self.generate_pki_archive_options(trans_wrapped_session_key, wrapped_secret)
 
-def print_key_request(request):
-    ''' Prints the relevant fields of a KeyRequestInfo object '''
-    print "RequestURL: " + str(request.requestURL)
-    print "RequestType: " + str(request.requestType)
-    print "RequestStatus: " + str(request.requestStatus)
-    print "KeyURL: " + str(request.keyURL)
-
-def print_key_info(key_info):
-    ''' Prints the relevant fields of a KeyInfo object '''
-    print "Key URL: " + str(key_info.keyURL)
-    print "Client ID: " + str(key_info.clientID)
-    print "Algorithm: " + str(key_info.algorithm)
-    print "Status: " + str(key_info.status)
-    print "Owner Name: " + str(key_info.ownerName)
-    print "Size: " + str(key_info.size)
-
-def print_key_data(key_data):
-    ''' Prints the relevant fields of a KeyData object '''
-    print "Key Algorithm: " + str(key_data.algorithm)
-    print "Key Size: " + str(key_data.size)
-    print "Nonce Data: " + str(key_data.nonceData)
-    print "Wrapped Private Data: " + str(key_data.wrappedPrivateData)
-
-def generate_symmetric_key(mechanism):
-    ''' generate symmetric key - to be moved to nssutil module'''
-    slot = nss.get_best_slot(mechanism)
-    return slot.key_gen(mechanism, None, slot.get_best_key_length(mechanism))
-
-def trans_wrap_sym_key(transport_cert, sym_key, mechanism):
-    ''' wrap a sym key with a transport cert - to be moved to nsutil module'''
-    public_key = transport_cert.subject_public_key_info.public_key
-    return base64.b64encode(nss.pub_wrap_sym_key(mechanism, public_key, sym_key))
-
-def barbican_encode(kraclient, client_id, algorithm, key_size, usage_string):
-    response = kraclient.generate_sym_key(client_id, algorithm, key_size, usage_string)
-    return response.requestInfo.get_key_id()
-
-def barbican_decode(kraclient, key_id, wrapped_session_key):
-    response = kraclient.request_recovery(key_id)
-    recovery_request_id = response.requestInfo.get_request_id()
-    kraclient.approve_request(recovery_request_id)
-    return kraclient.retrieve_key(key_id, recovery_request_id, wrapped_session_key)
-
-def main():
-    ''' test code execution '''
-    connection = client.PKIConnection('https', 'localhost', '8443', 'kra')
-    connection.set_authentication_cert('/tmp/temp4.pem')
-    kraclient = KRAClient(connection)
-    # Get Transport Cert
-    transport_cert = kraclient.get_transport_cert()
-    print transport_cert
-
-    print "Now getting key request"
-    keyrequest = kraclient.get_request('2')
-    print_key_request(keyrequest)
-
-    print "Now listing requests"
-    keyrequests = kraclient.list_requests('complete', 'securityDataRecovery')
-    print keyrequests.key_requests
-    for request in keyrequests.key_requests:
-        print_key_request(request)
-
-    print "Now generating symkey"
-    client_id = "Vek #1" + time.strftime('%X %x %Z')
-    algorithm = "AES"
-    key_size = 128
-    usages = [key.SymKeyGenerationRequest.DECRYPT_USAGE, key.SymKeyGenerationRequest.ENCRYPT_USAGE]
-    response = kraclient.generate_sym_key(client_id, algorithm, key_size, ','.join(usages))
-    print_key_request(response.requestInfo)
-    print "Request ID is " + response.requestInfo.get_request_id()
-    key_id = response.requestInfo.get_key_id()
-
-    print "Now getting key ID for clientID=\"" + client_id + "\""
-    key_infos = kraclient.list_keys(client_id, "active")
-    for key_info in key_infos.key_infos:
-        print_key_info(key_info)
-        key_id2 = key_info.get_key_id()
-    if key_id == key_id2:
-        print "Success! The keys from generation and search match."
-    else:
-        print "Failure - key_ids for generation do not match!"
-
-    print "Submit recovery request"
-    response = kraclient.request_recovery(key_id)
-    print response
-    print_key_request(response.requestInfo)
-    recovery_request_id = response.requestInfo.get_request_id()
-
-    print "Approve recovery request"
-    print kraclient.approve_request(recovery_request_id)
-
-    # now begins the nss specific code
-    # you need to have an nss database set up with the transport cert
-    # imported therein.
-    print "Retrieve key"
-    nss.nss_init("/tmp/drmtest/certdb")
-    mechanism = nss.CKM_DES3_CBC_PAD
-
-    transport_cert = nss.find_cert_from_nickname("kra transport cert")
-    session_key = generate_symmetric_key(mechanism)
-    print session_key
-    wrapped_session_key = trans_wrap_sym_key(transport_cert, session_key, nss.CKM_DES_CBC_PAD)
-
-    response = kraclient.retrieve_key(key_id, recovery_request_id, wrapped_session_key)
-    print_key_data(response)
-
-    # do the above again - but this time using Barbican -like encode() and decode() functions
-
-    # generate a symkey
-    client_id = "Barbican VEK #1" + time.strftime('%X %x %Z')
-    algorithm = "AES"
-    key_size = 128
-    usages = [key.SymKeyGenerationRequest.DECRYPT_USAGE, key.SymKeyGenerationRequest.ENCRYPT_USAGE]
-    key_id = barbican_encode(kraclient, client_id, algorithm, key_size, ','.join(usages))
-    print "barbican_encode() returns " + str(key_id)
-
-    # recover the symkey
-    session_key = generate_symmetric_key(mechanism)
-    wrapped_session_key = trans_wrap_sym_key(transport_cert, session_key, nss.CKM_DES_CBC_PAD)
-    response = barbican_decode(kraclient, key_id, wrapped_session_key)
-    print "barbican_decode() returns:"
-    print_key_data(response)
-
-if __name__ == "__main__":
-    main()
diff --git a/base/common/python/pki/systemcert.py b/base/common/python/pki/systemcert.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa1cb538d234e707f7aff0838c6c4d5a3632008a
--- /dev/null
+++ b/base/common/python/pki/systemcert.py
@@ -0,0 +1,46 @@
+#!/usr/bin/python
+# Authors:
+#     Ade Lee <alee@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright (C) 2013 Red Hat, Inc.
+# All rights reserved.
+#
+'''
+Module containing the Python client classes for the SystemCert REST API
+'''
+
+from pki.cert import CertData
+
+class SystemCertClient(object):
+    '''
+    Class encapsulating and mirroring the functionality in the SystemCertResouce
+    Java interface class defining the REST API for system certificate resources.
+    '''
+
+    def __init__(self, connection):
+        ''' Constructor '''
+        #super(PKIResource, self).__init__(connection)
+        self.connection = connection
+        self.headers = {'Content-type': 'application/json',
+                        'Accept': 'application/json'}
+        self.cert_url = '/rest/config/cert'
+
+    def get_transport_cert(self):
+        ''' Return transport certificate '''
+        url = self.cert_url +  '/transport'
+        response = self.connection.get(url, self.headers)
+        cert_data = CertData.from_dict(response.json())
+        return cert_data.Encoded
diff --git a/base/kra/functional/drmclient.py b/base/kra/functional/drmclient_deprecated.py
similarity index 99%
rename from base/kra/functional/drmclient.py
rename to base/kra/functional/drmclient_deprecated.py
index 588b6088b9fe639e4fa48e552204062a31f0048c..602057f92601b21249f6f2f77b0452951a4b32ad 100644
--- a/base/kra/functional/drmclient.py
+++ b/base/kra/functional/drmclient_deprecated.py
@@ -18,6 +18,9 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 '''
+NOTE: This is an old deprecated client.  The REST interface has changed
+and so this code will likely NOT work.  It will be removed from the code
+base soon.
 
 ============================================================
 Python Test client for KRA using the new RESTful interface
@@ -572,7 +575,7 @@ class kra:
         :param passphrase         passphrase wrapped in session key
         :return doc:              xml doc with archival request
 
-    	"""
+	"""
         self.debug('%s.create_recovery_request()', self.fullname)
         root = etree.Element("KeyRecoveryRequest")
         if key_id != None:
@@ -878,7 +881,7 @@ class kra:
         |data             |String         | Key data (either wrapped using        |
         |                 |               | passphrase or unwrapped)              |
         +-----------------+---------------+---------------------------------------+
-    	"""
+	"""
         self.debug('%s.retrieve_security_data()', self.fullname)
 
         if recovery_request_id == None:
diff --git a/base/kra/functional/drmclient.readme.txt b/base/kra/functional/drmclient_deprecated.readme.txt
similarity index 95%
rename from base/kra/functional/drmclient.readme.txt
rename to base/kra/functional/drmclient_deprecated.readme.txt
index 833c5ce3cd5043455ac84b89a2780e8d8f4404c9..e9a69f95e35244f40c6a029eb41956c7b22021f9 100644
--- a/base/kra/functional/drmclient.readme.txt
+++ b/base/kra/functional/drmclient_deprecated.readme.txt
@@ -3,7 +3,7 @@ Running drmclient.py:
 The python drmclient currently requires a little setup to be run.
 
 1. Create a working directory - the code uses /tmp/drmtest
-2. In that directory, create an NSS database.  In this doc, we will use the 
+2. In that directory, create an NSS database.  In this doc, we will use the
    password redhat123 as the password for the NSS db.
 
    certutil -N -d /tmp/drmtest
@@ -19,7 +19,7 @@ The python drmclient currently requires a little setup to be run.
 5. Import the transport certificate into the certificate databse in /tmp/drmtest.
    certutil -A -d /tmp/drmtest -n "DRM Transport Certificate" -i /tmp/drmtest/transport.asc
 
-5. Run GeneratePKIArchiveOptions to generate some test data.   Specifically we will be 
+5. Run GeneratePKIArchiveOptions to generate some test data.   Specifically we will be
    using it to generate a symmetric key and its associated PKIArchoveOptions structure
    to be archived.
 
@@ -47,4 +47,4 @@ optional arguments:
   -n KRA_NICKNAME       DRM Nickname
 
 For example:
-python pki/base/kra/functional/drmclient.py -d /tmp/drmtest -p 10200 -n "DRM Transport Certificate - alee eclipse domain 2" 
+python pki/base/kra/functional/drmclient.py -d /tmp/drmtest -p 10200 -n "DRM Transport Certificate - alee eclipse domain 2"
diff --git a/base/kra/functional/drmtest.py b/base/kra/functional/drmtest.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c7e41244db2782c5fe4ee5407f765f8a689390b
--- /dev/null
+++ b/base/kra/functional/drmtest.py
@@ -0,0 +1,130 @@
+# Authors:
+#   Ade Lee <alee@redhat.com>
+#
+# Copyright (C) 2012  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+'''
+=========================================================================
+Python test code for interacting with the DRM using the RESTful interface
+=========================================================================
+
+This code is to be viewed as example code on how to interact with the DRM
+for Key and KeyRequest resources using the Python REST client framework.
+
+Some setup is required to run the tests here successfully.
+See drmtest.readme.txt.
+'''
+
+import base64
+import pki.cryptoutil as cryptoutil
+import pki.key as key
+import time
+
+from pki.client import PKIConnection
+from pki.kraclient import KRAClient
+
+def print_key_request(request):
+    ''' Prints the relevant fields of a KeyRequestInfo object '''
+    print "RequestURL: " + str(request.requestURL)
+    print "RequestType: " + str(request.requestType)
+    print "RequestStatus: " + str(request.requestStatus)
+    print "KeyURL: " + str(request.keyURL)
+
+def print_key_info(key_info):
+    ''' Prints the relevant fields of a KeyInfo object '''
+    print "Key URL: " + str(key_info.keyURL)
+    print "Client ID: " + str(key_info.clientID)
+    print "Algorithm: " + str(key_info.algorithm)
+    print "Status: " + str(key_info.status)
+    print "Owner Name: " + str(key_info.ownerName)
+    print "Size: " + str(key_info.size)
+
+def print_key_data(key_data):
+    ''' Prints the relevant fields of a KeyData object '''
+    print "Key Algorithm: " + str(key_data.algorithm)
+    print "Key Size: " + str(key_data.size)
+    print "Nonce Data: " + str(key_data.nonceData)
+    print "Wrapped Private Data: " + str(key_data.wrappedPrivateData)
+
+def main():
+    ''' test code execution '''
+    connection = PKIConnection('https', 'localhost', '8443', 'kra')
+    connection.set_authentication_cert('/tmp/temp4.pem')
+    crypto = cryptoutil.NSSCryptoUtil("/tmp/drmtest/certdb", "redhat123")
+    kraclient = KRAClient(connection, crypto, "kra transport cert")
+
+    # Test 1: Get Transport Cert
+    transport_cert = kraclient.system_certs.get_transport_cert()
+    print transport_cert
+
+    # Test 2: Get key request info
+    print "Now getting key request"
+    keyrequest = kraclient.keys.get_request_info('2')
+    print_key_request(keyrequest)
+
+    # Test 3: List requests
+    print "Now listing some requests"
+    keyrequests = kraclient.keys.list_requests('complete', 'securityDataRecovery')
+    print keyrequests.key_requests
+    for request in keyrequests.key_requests:
+        print_key_request(request)
+
+    # Test 4: generate symkey -- same as barbican_encode()
+    print "Now generating symkey on KRA"
+    client_id = "Vek #1" + time.strftime('%X %x %Z')
+    algorithm = "AES"
+    key_size = 128
+    usages = [key.SymKeyGenerationRequest.DECRYPT_USAGE, key.SymKeyGenerationRequest.ENCRYPT_USAGE]
+    response = kraclient.generate_sym_key(client_id, algorithm, key_size, usages)
+    print_key_request(response.requestInfo)
+    print "Request ID is " + response.requestInfo.get_request_id()
+    key_id = response.get_key_id()
+
+    # Test 5: Confirm the key_id matches
+    print "Now getting key ID for clientID=\"" + client_id + "\""
+    key_infos = kraclient.keys.list_keys(client_id=client_id, status="active")
+    for key_info in key_infos.key_infos:
+        print_key_info(key_info)
+        key_id2 = key_info.get_key_id()
+    if key_id == key_id2:
+        print "Success! The keys from generation and search match."
+    else:
+        print "Failure - key_ids for generation do not match!"
+
+    # Test 6: Barbican_decode() - Retrieve while providing trans_wrapped_session_key
+    session_key = crypto.generate_symmetric_key()
+    wrapped_session_key = crypto.asymmetric_wrap(session_key, kraclient.transport_cert)
+    key_data, _unwrapped_key = kraclient.retrieve_key(key_id, trans_wrapped_session_key=wrapped_session_key)
+    print_key_data(key_data)
+    unwrapped_key = crypto.symmetric_unwrap(key_data.wrappedPrivateData, session_key, iv=key_data.nonceData)
+    key1 = base64.encodestring(unwrapped_key)
+
+    # Test 7: Recover key without providing trans_wrapped_session_key
+    key_data, unwrapped_key = kraclient.retrieve_key(key_id)
+    print_key_data(key_data)
+    key2 = base64.encodestring(unwrapped_key)
+
+    # Test 8 - Confirm that keys returned are the same
+    if key1 == key2:
+        print "Success: The keys returned match! Key = " + str(key1)
+    else:
+        print "Failure: The returned keys do not match!"
+        print "key1: " + key1
+        print "key2: " + key2
+
+if __name__ == "__main__":
+    main()
diff --git a/base/kra/functional/drmtest.readme.txt b/base/kra/functional/drmtest.readme.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4e5c5f308dec9e2bf8d406752a378d9827a6369d
--- /dev/null
+++ b/base/kra/functional/drmtest.readme.txt
@@ -0,0 +1,28 @@
+You will need to set up a few things first though:
+1.  Install a CA/KRA.  It this is not on the default ports, you will
+need to modify the connection information in KRAClient.__main__
+
+2. The python code uses python-requests to talk to the server, and
+requests uses openssl.  That means you need to export your DRM admin
+cert to a PEM file, so that it can be used for client auth.  I did this
+as follows:
+
+ openssl pkcs12 -in ~/.dogtag/pki-tomcat/ca_admin_cert.p12 -out /tmp/temp4.pem -nodes
+
+Without any changes, the code in KRAClient.__main__ assumes this file
+will be in /tmp/temp4.pem.
+
+3.  We do some crypto functions using NSS commands (like generating a
+symmetric key or wrapping using the transport cert).  Therefore, we need
+to create an NSS database and populate it with the transport cert.  The
+code expects it to be at /tmp/drmtest/certdb
+
+I did this as follows:
+mkdir -p /tmp/drmtest/certdb
+certutil -N -d /tmp/drmtest/certdb
+chmod +r /tmp/drmtest/certdb/*
+
+certutil -L -d /var/lib/pki/pki-tomcat/alias/ -n "transportCert cert-pki-tomcat KRA" -a > transport_cert.txt
+certutil -A -d /tmp/drmtest/certdb/ -n "kra transport cert" -i ./transport_cert.txt -a -t "u,u,u"
+
+4. Then just run kraclient.__main__ with no arguments.
\ No newline at end of file
-- 
1.8.4.2

