On 2/12/2014 2:41 PM, Ade Lee wrote:
Hi all,
Attached is a patch with some initial work for the Python client
library, specifically focused on the interactions between the DRM and
the python client, and the functionality exposed in the
KeyRequestResource and KeyResource REST APIs.
The easiest way to see this code in action is to run the main function
in the KRAClient module. I usually run this in Eclipse.
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 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 /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.
The story is still somewhat incomplete, but its a lot of code - so I
want to start getting some eyes on this.
Here is what I plan to add in subsequent patches:
1. Extract the NSS functionality into a separate module nssutil.py, and
import that module only if python-nss is actually installed. Similarly,
only define those functions that need nss if python-nss is installed.
The idea here is that the client library does not necessarily require
NSS. If NSS (and python-nss) is not installed, then the client needs to
provide things like symmetric keys and wrapping etc.
This will almost certainly be the case for Barbican, where they will
generate their own keys, and do their own wrapping.
2. Complete the generate_pki_archive_options() function, and then test
archival.
3. Add logic to handle exceptions. This will almost certainly take the
form of decorator classes that handle the various exceptions and return
codes expected.
4. The new modules pass most of the PEP8 conventions as detected by
pylint. One thing that pylint does not like is having attribute names
that are camelCased. The reason those are there is because they
represent the actual names/attributes as passed over in JSON by the
server. We populate the relevant objects by doing something like this:
def from_dict(self, attr_list):
''' Return KeyInfo from JSON dict '''
for key in attr_list:
setattr(self, key, attr_list[key])
return self
I plan to add a small conversion function that convert camelCased
attribute names to camel_cased to conform with PEP8.
Please review,
Ade
Some comments:
1. The 'created' date in cert.py should be updated.
2. The CertId.__init__() doesn't parse the hex number.
3. To be consistent with the Java API, all from_dict() methods probably
should be a @staticmethod or @classmethod.
4. To be consistent the decode_from_json() can be called from_json(). Is
there any difference between this method and from_dict()?
5. The CertResource should be called CertClient for consistency with
Java API. Same thing with KeyResource and KeyRequestResource. The
*Resource classes in Java are internal implementation. The classes
actually exposed to the client applications are the *Client classes.
6. The following line is executed each time getCert() is called:
e.TYPES['CertData'] = CertData()
It should be executed just once. See system.py.
7. The TYPES list in #6 holds references to classes, not instances. This
is probably related to #3 as well.
8. The listCerts() should have passed the status as a query parameter.
9. As in #6, the following line in searchCerts() should be executed just
once:
e.TYPES['CertSearchRequest'] = CertSearchRequest
10. I have not tested the code, but searchCerts() doesn't seem to work
with the new Jackson-style JSON format. See system.py.
11. The get_transport_cert() is defined in CertResource. In the Java API
getTransportCert() is defined in SystemCertResource and used by
KRAClient. There's no SystemCertClient yet, but this method should be
defined there.
12. KeyRequestResponse.decode_from_json() does not populate keyData
attribute.
13. As in #6, in KeyResource.retrieve_key() the e.NOTYPES registration
should only be executed once.
14. The key.py's main program is a sample of a client application using
the Python library, but it does the e.NOTYPES registrations. Although
the registrations are only executed once, any client application should
not need to do these registrations. The Python library itself should do
the registrations automatically. See system.py.
15. It would be better to move the main programs in these Python library
into a separate test folder because they become functional tests and
contain hard-coded test configuration (e.g. hostname, port, path,
request ID, client ID). Please include the instruction to setup the test
environment as mentioned in the above email.
16. From client application's perspective, it would be better if the
kraclient.generate_sym_key() can take a list of usages, instead of
requiring the client app to join the usages manually.
17. Ideally the Key/KeyRequest-specific methods in KRAClient should be
moved into KeyClient/KeyRequestClient classes to avoid cluttering up the
KRAClient class. In the Java client library user-specific methods are
grouped into UserClient under KRAClient.
18. Instead of adding barbican_encode() which basically wraps
generate_sym_key(), the KeyRequestResponse could provide get_key_id()
which will get the key ID from the requestInfo so the client app can
call generate_sym_key() directly:
response = kraclient.generate_sym_key(...)
key_id = response.get_key_id()
19. Instead of adding barbican_decode(), the KRAClient could provide a
more generic method that can be used by other client applications.
Something like this:
class KRAClient(object):
def retrieve_key(self, key_id):
response = request_recovery(key_id)
approve_request(response.get_request_id())
transport_cert = get_transport_cert()
session_key = generate_session_key(...)
wrapped_session_key = wrap_session_key(...)
request = KeyRecoveryRequest(key_id, ...)
response = key_client.retrieve_key(request)
return unwrap_key(response.wrappedPrivateData, ...))
Client apps such as Barbican would be able to subclass and override the
generate_session_key() to provide its own key, possibly using other
crypto library.
--
Endi S. Dewata