diff options
-rw-r--r-- | .pydevproject | 1 | ||||
-rw-r--r-- | base/common/python/pki/cert.py | 219 | ||||
-rw-r--r-- | base/common/python/pki/client.py | 28 | ||||
-rw-r--r-- | base/common/python/pki/key.py | 458 | ||||
-rw-r--r-- | base/common/python/pki/kraclient.py | 386 | ||||
-rw-r--r-- | base/kra/functional/drmclient.py | 20 |
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, |