summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.pydevproject1
-rw-r--r--base/common/python/pki/cert.py219
-rw-r--r--base/common/python/pki/client.py28
-rw-r--r--base/common/python/pki/key.py458
-rw-r--r--base/common/python/pki/kraclient.py386
-rw-r--r--base/kra/functional/drmclient.py20
6 files changed, 1097 insertions, 15 deletions
diff --git a/.pydevproject b/.pydevproject
index 4a8d2616b..fdc8738e1 100644
--- a/.pydevproject
+++ b/.pydevproject
@@ -6,5 +6,6 @@
<path>/pki/base/common/python/pki</path>
<path>/pki/base/server/python/pki/server</path>
<path>/pki/base/kra/functional</path>
+<path>/pki</path>
</pydev_pathproperty>
</pydev_project>
diff --git a/base/common/python/pki/cert.py b/base/common/python/pki/cert.py
new file mode 100644
index 000000000..6b8994e15
--- /dev/null
+++ b/base/common/python/pki/cert.py
@@ -0,0 +1,219 @@
+#!/usr/bin/python
+'''
+Created on Aug 27, 2013
+
+@author: akoneru
+'''
+import pki.client as client
+import pki.encoder as e
+import json
+import types
+
+class CertId(object):
+ '''
+ Class encapsulating a certificate serial number
+ '''
+
+ def __init__(self, cert_id):
+ ''' Constructor '''
+ if str(cert_id).startswith('0x'):
+ #hex number
+ print 'hex number'
+ self.id = cert_id
+ else:
+ self.id = cert_id
+
+class CertData(object):
+ '''
+ Class containing certificate data as returned from getCert()
+ '''
+
+ def __init__(self):
+ ''' Constructor '''
+ self.Encoded = None
+
+ def from_dict(self, attr_list):
+ ''' Return CertData object from JSON dict '''
+ for key in attr_list:
+ setattr(self, key, attr_list[key])
+ return self
+
+class CertDataInfo(object):
+ '''
+ Class containing information contained in a CertRecord on the CA.
+ This data is returned when searching/listing certificate records.
+ '''
+
+ def __init__(self):
+ ''' Constructor '''
+ self.certId = None
+ self.subjectDN = None
+ self.status = None
+ self.type = None
+ self.version = None
+ self.keyAlgorithmOID = None
+ self.keyLength = None
+ self.notValidBefore = None
+ self.notValidAfter = None
+ self.issuedOn = None
+ self.issuedBy = None
+
+ def from_dict(self, attr_list):
+ ''' Return CertDataInfo object from JSON dict '''
+ for key in attr_list:
+ setattr(self, key, attr_list[key])
+ return self
+
+class CertDataInfos(object):
+ '''
+ Class containing lists of CertDataInfo objects.
+ This data is returned when searching/listing certificate records on the CA.
+ '''
+
+ def __init__(self):
+ ''' Constructor '''
+ self.certInfoList = []
+ self.links = []
+
+ def decode_from_json(self, json_value):
+ ''' Populate object from JSON input '''
+ cert_infos = json_value['CertDataInfo']
+ if not isinstance(cert_infos, types.ListType):
+ certInfo = CertDataInfo()
+ self.certInfoList.append(certInfo.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)
+
+class CertSearchRequest(object):
+
+ def __init__(self):
+ self.serialNumberRangeInUse = False
+ self.serialTo = None
+ self.serialFrom = None
+ self.subjectInUse = False
+ self.eMail = None
+ self.commonName = None
+ self.userID = None
+ self.orgUnit = None
+ self.org = None
+ self.locality = None
+ self.state = None
+ self.country = None
+ self.matchExactly = None
+ self.status = None
+ self.revokedBy = None
+ self.revokedOnFrom = None
+ self.revokedOnTo = None
+ self.revocationReason = None
+ self.issuedBy = None
+ self.issuedOnFrom = None
+ self.issuedOnTo = None
+ self.validNotBeforeFrom = None
+ self.validNotBeforeTo = None
+ self.validNotAfterFrom = None
+ self.validNotAfterTo = None
+ self.validityOperation = None
+ self.validityCount = None
+ self.validityUnit = None
+ self.certTypeSubEmailCA = None
+ self.certTypeSubSSLCA = None
+ self.certTypeSecureEmail = None
+ self.certTypeSSLClient = None
+ self.certTypeSSLServer = None
+ self.revokedByInUse = False
+ self.revokedOnInUse = False
+ self.revocationReasonInUse = None
+ self.issuedByInUse = False
+ self.issuedOnInUse = False
+ self.validNotBeforeInUse = False
+ self.validNotAfterInUse = False
+ self.validityLengthInUse = False
+ self.certTypeInUse = False
+
+
+class CertResource(object):
+ '''
+ Class encapsulating and mirroring the functionality in the CertResouce Java interface class
+ defining the REST API for 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/certs'
+ self.agent_cert_url = '/rest/agent/certs'
+
+ def getCert(self, cert_id):
+ ''' 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())
+ return certData
+
+ def listCerts(self, status = None):
+ ''' Return a CertDataInfos object for specific certs
+ Not sure I understand what this method is for.
+ '''
+ if status is not None:
+ cert_search_request = CertSearchRequest()
+ cert_search_request.status = status
+
+ response = self.connection.get(self.cert_url, self.headers)
+ print response.json()
+
+ 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)
+ r = self.connection.post(url, searchRequest, self.headers)
+ print r.json()['CertDataInfos']
+ cdis = CertDataInfos()
+ cdis.decode_from_json(r.json()['CertDataInfos'])
+ return cdis
+
+ def getCerts(self, cert_search_request):
+ ''' Doctring needed here. '''
+ pass
+
+ def reviewCert(self, cert_id):
+ ''' Doc string needed here. '''
+ pass
+
+ def revokeCert(self, cert_id, cert_revoke_request):
+ ''' Doc string needed here '''
+ pass
+
+ def revokeCACert(self, cert_id, cert_revoke_request):
+ ''' Doc string needed here. '''
+ pass
+
+ def unrevokecert(self, cert_id, cert_unrevoke_request):
+ ''' Doc string needed here '''
+ pass
+
+
+ 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)
+ cert = certResource.getCert(CertId('0x6'))
+ print cert
+
+if __name__ == "__main__":
+ main()
diff --git a/base/common/python/pki/client.py b/base/common/python/pki/client.py
index 00343bb7c..c95cb2927 100644
--- a/base/common/python/pki/client.py
+++ b/base/common/python/pki/client.py
@@ -26,7 +26,7 @@ class PKIConnection:
def __init__(self,
protocol='http',
hostname='localhost',
- port=80,
+ port='8080',
subsystem='ca',
accept='application/json'):
@@ -47,19 +47,37 @@ class PKIConnection:
if username is not None and password is not None:
self.session.auth = (username, password)
- def get(self, path, headers=None):
+ def set_authentication_cert(self, pem_cert_path):
+ if pem_cert_path is None:
+ raise Exception("No path for the certificate specified.")
+ if len(str(pem_cert_path)) == 0:
+ raise Exception("No path for the certificate specified.")
+ self.session.cert = pem_cert_path
+
+ def get(self, path, headers=None, params=None):
r = self.session.get(
self.serverURI + path,
verify=False,
- headers=headers)
+ headers=headers,
+ params=params)
r.raise_for_status()
return r
- def post(self, path, payload, headers=None):
+ def post(self, path, payload, headers=None, params=None):
r = self.session.post(
self.serverURI + path,
verify=False,
data=payload,
- headers=headers)
+ headers=headers,
+ params=params)
r.raise_for_status()
return r
+def main():
+ conn = PKIConnection()
+ headers = {'Content-type': 'application/json',
+ 'Accept': 'application/json'}
+ conn.set_authentication_cert('/root/temp4.pem')
+ print conn.get("", headers).json()
+
+if __name__ == "__main__":
+ main()
diff --git a/base/common/python/pki/key.py b/base/common/python/pki/key.py
new file mode 100644
index 000000000..a55696cb3
--- /dev/null
+++ b/base/common/python/pki/key.py
@@ -0,0 +1,458 @@
+#!/usr/bin/python
+# Authors:
+# Abhishek Koneru <akoneru@redhat.com>
+# 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 KeyResource and
+KeyRequestResource REST API on a DRM
+'''
+import pki.encoder as e
+import json
+import types
+
+class KeyId(object):
+ '''
+ Class representing a key ID
+ '''
+ def __init__(self, key_id=None):
+ ''' Constructor '''
+ self.value = key_id
+
+#should be moved to request.py
+class RequestId(object):
+ '''
+ Class representing a Request ID
+ '''
+ def __init__(self, req_id):
+ ''' Constructor'''
+ self.value = req_id
+
+class KeyData(object):
+ '''
+ This is the object that contains the wrapped secret
+ when that secret is retrieved.
+ '''
+
+ def __init__(self):
+ ''' Constructor '''
+ self.algorithm = None
+ self.nonceData = None
+ self.size = None
+ self.wrappedPrivateData = None
+
+ def from_dict(self, attr_list):
+ ''' Return a KeyData object from a JSON dict '''
+ for key in attr_list:
+ setattr(self, key, attr_list[key])
+ return self
+
+class KeyInfo(object):
+ '''
+ This is the object that contains information stored
+ in the databse record for an archived secret. It does not
+ contain the secret itself.
+ '''
+
+ def __init__(self):
+ ''' Constructor '''
+ self.clientID = None
+ self.keyURL = None
+ self.algorithm = None
+ self.status = None
+ self.ownerName = None
+ self.size = None
+
+ def from_dict(self, attr_list):
+ ''' Return KeyInfo from JSON dict '''
+ for key in attr_list:
+ setattr(self, key, attr_list[key])
+ return self
+
+ def get_key_id(self):
+ ''' Return the key ID as parsed from key URL '''
+ if self.keyURL != None:
+ indx = str(self.keyURL).rfind("/") + 1
+ return str(self.keyURL)[indx:]
+ return None
+
+
+class KeyInfoCollection(object):
+ '''
+ This class represents data returned when searching the DRM archived
+ secrets. Essentially, its a list of KeyInfo objects.
+ '''
+
+ def __init__(self):
+ ''' Constructor '''
+ self.key_infos = []
+ self.links = []
+
+ def decode_from_json(self, json_value):
+ ''' Populate the object from its JSON representation '''
+ infos = json_value['entries']
+ if not isinstance(infos, types.ListType):
+ info = KeyInfo()
+ self.key_infos.append(info.from_dict(infos))
+ else:
+ for info in infos:
+ key_info = KeyInfo()
+ key_info.from_dict(info)
+ self.key_infos.append(key_info)
+
+class KeyRequestInfo(object):
+ '''
+ This class represents data about key requests (archival, recovery,
+ key generation etc.) in the DRM.
+ '''
+
+ def __init__(self):
+ ''' Constructor '''
+ self.requestURL = None
+ self.requestType = None
+ self.keyURL = None
+ self.requestStatus = None
+
+ def from_dict(self, attr_list):
+ ''' Return a KeyRequestInfo object from a JSON dict. '''
+ for key in attr_list:
+ setattr(self, key, attr_list[key])
+ return self
+
+ def get_request_id(self):
+ ''' Return the request ID by parsing the request URL. '''
+ if self.requestURL != None:
+ indx = str(self.requestURL).rfind("/") + 1
+ return str(self.requestURL)[indx:]
+ return None
+
+ def get_key_id(self):
+ ''' Return the ID of the secret referred to by this request. '''
+ if self.keyURL != None:
+ indx = str(self.keyURL).rfind("/") + 1
+ return str(self.keyURL)[indx:]
+ return None
+
+class KeyRequestInfoCollection(object):
+ '''
+ This class represents the data returned when searching the key
+ requests in the DRM. Essentially, its a list of KeyRequestInfo
+ objects.
+ '''
+
+ def __init__(self):
+ ''' Constructor '''
+ self.key_requests = []
+ self.links = []
+
+ def decode_from_json(self, json_value):
+ ''' Populate the object from its JSON representation. '''
+ infos = json_value['entries']
+ if not isinstance(infos, types.ListType):
+ info = KeyRequestInfo()
+ self.key_requests.append(info.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)
+
+class KeyRequestResponse(object):
+ '''
+ This class is returned when an archival, recovery or key generation
+ request is created. It includes a KeyRequestInfo object with
+ information about the created request, and a KeyData structure
+ which contains the wrapped secret (if that operation is supported).
+ '''
+
+ def __init__(self):
+ ''' Constructor '''
+ 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'])
+
+class Attribute(object):
+ '''
+ Class representing a key/value pair.
+
+ This object is the basis of the representation of a ResourceMessage.
+ '''
+
+ def __init__(self, name, value):
+ ''' Constructor '''
+ self.name = name
+ self.value = value
+
+class AttributeList(object):
+ '''
+ Class representing a list of attributes.
+
+ This class is needed because of a JavaMapper used in the REST API.
+ '''
+
+ def __init__(self):
+ ''' Constructor '''
+ self.Attribute = []
+
+class ResourceMessage(object):
+ '''
+ This class is the basis for the various types of key requests.
+ It is essentially a list of attributes.
+ '''
+
+ def __init__(self, class_name):
+ ''' Constructor '''
+ self.Attributes = AttributeList()
+ self.ClassName = class_name
+
+ def add_attribute(self, name, value):
+ ''' Add an attribute to the list. '''
+ attr = Attribute(name, value)
+ self.Attributes.Attribute.append(attr)
+
+ def get_attribute_value(self, name):
+ ''' Get the value of a given attribute '''
+ for attr in self.Attributes.Attribute:
+ if attr.name == name:
+ return attr.value
+ return None
+
+class KeyArchivalRequest(ResourceMessage):
+ '''
+ Class representing the object sent to the DRM when archiving a secret.
+ '''
+
+ def __init__(self, client_id=None, data_type=None, wrapped_private_data=None,
+ key_algorithm=None, key_size=None):
+ ''' Constructor '''
+ ResourceMessage.__init__(self,
+ "com.netscape.certsrv.key.KeyArchivalRequest")
+ self.add_attribute("clientID", client_id)
+ self.add_attribute("dataType", data_type)
+ self.add_attribute("wrappedPrivateData", wrapped_private_data)
+ self.add_attribute("keyAlgorithm", key_algorithm)
+ self.add_attribute("keySize", key_size)
+
+class KeyRecoveryRequest(ResourceMessage):
+ '''
+ Class representing the data sent to the DRM when either creating a request
+ for the recovery of a secret, or, once the request is approved, retrieving
+ the secret.
+ '''
+
+ def __init__(self, key_id=None, request_id=None,
+ trans_wrapped_session_key=None,
+ session_wrapped_passphrase=None,
+ nonce_data=None, certificate=None,
+ passphrase=None):
+ ''' Constructor '''
+ ResourceMessage.__init__(self,
+ "com.netscape.certsrv.key.KeyRecoveryRequest")
+ self.add_attribute("requestId", request_id)
+ self.add_attribute("transWrappedSessionKey", trans_wrapped_session_key)
+ self.add_attribute("sessionWrappedPassphrase", session_wrapped_passphrase)
+ self.add_attribute("nonceData", nonce_data)
+ self.add_attribute("certificate", certificate)
+ self.add_attribute("passphrase", passphrase)
+ self.add_attribute("keyId", key_id)
+
+class SymKeyGenerationRequest(ResourceMessage):
+ '''
+ Class representing the data sent to the DRM when generating and archiving
+ a symmetric key on the DRM.
+ '''
+
+ UWRAP_USAGE = "unwrap"
+ WRAP_USAGE = "wrap"
+ VERIFY_USAGE = "verify"
+ SIGN_USAGE = "sign"
+ DECRYPT_USAGE = "decrypt"
+ ENCRYPT_USAGE = "encrypt"
+
+ def __init__(self, client_id=None, key_size=None, key_algorithm=None,
+ key_usage=None):
+ ''' Constructor '''
+ ResourceMessage.__init__(self,
+ "com.netscape.certsrv.key.SymKeyGenerationRequest")
+ self.add_attribute("clientID", client_id)
+ self.add_attribute("keySize", key_size)
+ self.add_attribute("keyAlgorithm", key_algorithm)
+ self.add_attribute("keyUsage", key_usage)
+
+class KeyResource(object):
+ '''
+ Class that encapsulates and mirrors the functions in the KeyResource
+ Java class in the DRM REST API.
+ '''
+
+ def __init__(self, connection):
+ ''' Constructor '''
+ self.connection = connection
+ self.headers = {'Content-type': 'application/json',
+ 'Accept': 'application/json'}
+ self.keyURL = '/rest/agent/keys'
+
+ def list_keys(self, client_id=None, status=None, max_results=None,
+ max_time=None, start=None, size=None):
+ ''' List/Search archived secrets in the DRM.
+
+ See KRAClient.list_keys for the valid values of status.
+ Returns a KeyInfoCollection object.
+ '''
+ query_params = {'clientID':client_id, 'status':status,
+ '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
+
+ def retrieve_key(self, data):
+ ''' Retrieve a secret from the DRM.
+
+ @param: data - a KeyRecoveryRequest containing the keyId of the
+ secret being retrieved, the request_id of the approved recovery
+ request and a wrapping mechanism. More details at
+ KRAClient.retrieve_key.
+
+ 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)
+ response = self.connection.post(url, keyRequest, self.headers)
+ keydata = KeyData()
+ keydata.from_dict(response.json())
+ return keydata
+
+class KeyRequestResource(object):
+ '''
+ Class that encapsulates and mirrors the functions in the KeyRequestResource
+ Java class in the DRM REST API/
+ '''
+
+ def __init__(self, connection):
+ ''' Constructor '''
+ self.connection = connection
+ self.headers = {'Content-type': 'application/json',
+ 'Accept': 'application/json'}
+ self.keyRequestsURL = '/rest/agent/keyrequests'
+
+ def list_requests(self, request_state=None, request_type=None, client_id=None,
+ start=None, page_size=None, max_results=None, max_time=None):
+ ''' List/Search key requests in the DRM.
+
+ See KRAClient.list_requests for the valid values of request_state and
+ request_type. Returns a KeyRequestInfoCollection object.
+ '''
+ query_params = {'requestState':request_state, 'requestType':request_type,
+ 'clientID':client_id, 'start':start, 'pageSize':page_size,
+ '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
+
+ def get_request_info(self, request_id):
+ ''' Return a KeyRequestInfo object for a specific request. '''
+ url = self.keyRequestsURL + '/' + request_id.value
+ response = self.connection.get(url, self.headers)
+ info = KeyRequestInfo()
+ info.from_dict(response.json())
+ return info
+
+ def create_request(self, request):
+ ''' Submit an archival, recovery or key generation request
+ to the DRM.
+
+ @param request - is either a KeyArchivalRequest,
+ KeyRecoverRequest or SymKeyGenerationRequest.
+
+ 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
+
+ def approve_request(self, request_id):
+ ''' Approve a secret recovery request '''
+ url = self.keyRequestsURL + '/' + request_id.value + '/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'
+ 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'
+ return self.connection.post(url, self.headers)
+
+
+def main():
+ 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 "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 "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)
+
+if __name__ == '__main__':
+ main()
diff --git a/base/common/python/pki/kraclient.py b/base/common/python/pki/kraclient.py
new file mode 100644
index 000000000..0cb6707ef
--- /dev/null
+++ b/base/common/python/pki/kraclient.py
@@ -0,0 +1,386 @@
+#!/usr/bin/python
+# Authors:
+# Abhishek Koneru <akoneru@redhat.com>
+# 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 KRAClient class. This class should be used by Python clients
+to interact with the DRM to expose the functionality of the KeyResource 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
+
+class KRAClient(object):
+ '''
+ Client class that models interactions with a KRA using the Key and KeyRequest REST APIs.
+ '''
+
+ def __init__(self, connection):
+ ''' Constructor '''
+ self.connection = connection
+ self.key_resource = key.KeyResource(connection)
+ self.key_request_resource = key.KeyRequestResource(connection)
+ self.cert_resource = cert.CertResource(connection)
+
+ # nss parameters
+ self.certdb_dir = None
+ self.certdb_password = None
+ self.transport_nick = None
+ self.transport_cert = None
+
+ def initialize_nss(self, certdb_dir, certdb_password, transport_nick):
+ ''' Initialize nss and nss related parameters
+
+ We expect this method to be called when an nss database is to
+ be used to do client side cryptographic operations.
+
+ 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
+ '''
+ return self.key_resource.list_keys(client_id, status)
+
+ 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 = 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.
+ '''
+ 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)
+
+ def generate_sym_key(self, client_id, algorithm, size, usages):
+ ''' Generate and archive a symmetric key on the DRM.
+
+ Return a KeyRequestResponse which contains a KeyRequestInfo
+ object that describes the URL for the request and generated key.
+ '''
+ request = key.SymKeyGenerationRequest(client_id=client_id,
+ key_size=size,
+ key_algorithm=algorithm,
+ key_usage=usages)
+ return self.key_request_resource.create_request(request)
+
+ def archive_key(self, client_id, data_type, wrapped_private_data,
+ key_algorithm=None, key_size=None):
+ ''' Archive a secret (symetric 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 = 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)
+
+ def generate_pki_archive_options(self, trans_wrapped_session_key, session_wrapped_secret):
+ ''' Return a PKIArchiveOptions structure for archiving a secret
+
+ Takes in a session key wrapped by the DRM transport certificate,
+ and a secret wrapped with the session key and creates a PKIArchiveOptions
+ structure to be used when archiving a secret
+ '''
+ 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.
+
+ This method uses NSS calls to do the following:
+ 1) generate a session key
+ 2) wrap the session key with the transport key
+ 3) wrap the secret with the session key
+ 4) create the PKIArchiveOptions structure using the results of
+ (2) and (3)
+
+ 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()
+
+ 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/kra/functional/drmclient.py b/base/kra/functional/drmclient.py
index 6e3a2ac61..588b6088b 100644
--- a/base/kra/functional/drmclient.py
+++ b/base/kra/functional/drmclient.py
@@ -503,7 +503,7 @@ class kra:
Wrap (encrypt) data using the supplied symmetric key
"""
- encoding_ctx, decoding_ctx = self.setup_contexts(self.mechanism, wrapping_key, self.iv)
+ encoding_ctx, _decoding_ctx = self.setup_contexts(self.mechanism, wrapping_key, self.iv)
wrapped_data = encoding_ctx.cipher_op(data) + encoding_ctx.digest_final()
return wrapped_data
@@ -526,7 +526,7 @@ class kra:
"""
if iv == None:
iv = self.iv
- encoding_ctx, decoding_ctx = self.setup_contexts(self.mechanism, wrapping_key, iv)
+ _encoding_ctx, decoding_ctx = self.setup_contexts(self.mechanism, wrapping_key, iv)
unwrapped_data = decoding_ctx.cipher_op(data) + decoding_ctx.digest_final()
return unwrapped_data
@@ -615,7 +615,7 @@ class kra:
request = self.create_archival_request(client_id, security_data, data_type)
# Call CMS
- http_status, http_reason_phrase, http_headers, http_body = \
+ http_status, http_reason_phrase, _http_headers, http_body = \
self._request('/kra/rest/agent/keyrequests/archive',
self.kra_agent_port,
self.POST,
@@ -678,7 +678,7 @@ class kra:
get_args = get_args + "&start=" + quote_plus(next_id)
# Call CMS
- http_status, http_reason_phrase, http_headers, http_body = \
+ http_status, http_reason_phrase, _http_headers, http_body = \
self._request('/kra/rest/agent/keys',
self.kra_agent_port,
self.GET,
@@ -720,7 +720,7 @@ class kra:
get_args = get_args + "&start=" + quote_plus(next_id)
# Call CMS
- http_status, http_reason_phrase, http_headers, http_body = \
+ http_status, http_reason_phrase, _http_headers, http_body = \
self._request('/kra/rest/agent/keyrequests',
self.kra_agent_port,
self.GET,
@@ -753,7 +753,7 @@ class kra:
request = self.create_recovery_request(key_id, None, None, None)
# Call CMS
- http_status, http_reason_phrase, http_headers, http_body = \
+ http_status, http_reason_phrase, _http_headers, http_body = \
self._request('/kra/rest/agent/keyrequests/recover',
self.kra_agent_port,
self.POST,
@@ -801,7 +801,7 @@ class kra:
raise CertificateOperationError(error=_('Bad argument to approve_recovery_request'))
# Call CMS
- http_status, http_reason_phrase, http_headers, http_body = \
+ http_status, http_reason_phrase, _http_headers, _http_body = \
self._request('/kra/rest/agent/keyrequests/' + request_id + '/approve',
self.kra_agent_port,
self.POST,
@@ -823,7 +823,7 @@ class kra:
raise CertificateOperationError(error=_('Bad argument to reject_recovery_request'))
# Call CMS
- http_status, http_reason_phrase, http_headers, http_body = \
+ http_status, http_reason_phrase, _http_headers, _http_body = \
self._request('/kra/rest/agent/keyrequests/' + request_id + '/reject',
self.kra_agent_port,
self.POST,
@@ -845,7 +845,7 @@ class kra:
raise CertificateOperationError(error=_('Bad argument to cancel_recovery_request'))
# Call CMS
- http_status, http_reason_phrase, http_headers, http_body = \
+ http_status, http_reason_phrase, _http_headers, _http_body = \
self._request('/kra/rest/agent/keyrequests/' + request_id + '/cancel',
self.kra_agent_port,
self.POST,
@@ -901,7 +901,7 @@ class kra:
wrapped_passphrase)
# Call CMS
- http_status, http_reason_phrase, http_headers, http_body = \
+ http_status, http_reason_phrase, _http_headers, http_body = \
self._request('/kra/rest/agent/keys/retrieve',
self.kra_agent_port,
self.POST,